review: Codex 5.4 findings — 2 P1s, 2 P2s#5
Merged
cael-dandelion-cult merged 1 commit intoMar 6, 2026
Merged
Conversation
cael-dandelion-cult
merged commit Mar 6, 2026
5614bf8
into
feature/context-pressure-squashed
2 of 9 checks passed
cael-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 2, 2026
* feat: add QQ Bot channel extension * fix(qqbot): add setupWizard to runtime plugin for onboard re-entry * fix: fix review * fix: fix review * chore: sync lockfile and config-docs baseline for qqbot extension * refactor: 移除图床服务器相关代码 * fix * docs: 新增 QQ Bot 插件文档并修正链接路径 * refactor: remove credential backup functionality and update setup logic - Deleted the credential backup module to streamline the codebase. - Updated the setup surface to handle client secrets more robustly, allowing for configured secret inputs. - Simplified slash commands by removing unused hot upgrade compatibility checks and related functions. - Adjusted types to use SecretInput for client secrets in QQBot configuration. - Modified bundled plugin metadata to allow additional properties in the config schema. * feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理 * feat: 添加本地媒体路径解析功能,修正 QQBot 媒体路径处理 * feat: remove qqbot-media and qqbot-remind skills, add tests for config and setup - Deleted the qqbot-media and qqbot-remind skills documentation files. - Added unit tests for qqbot configuration and setup processes, ensuring proper handling of SecretRef-backed credentials and account configurations. - Implemented tests for local media path remapping, verifying correct resolution of media file paths. - Removed obsolete channel and remind tools, streamlining the codebase. * feat: 更新 QQBot 配置模式,添加音频格式和账户定义 * feat: 添加 QQBot 频道管理和定时提醒技能,更新媒体路径解析功能 * fix * feat: 添加 /bot-upgrade 指令以查看 QQBot 插件升级指引 * feat: update reminder and qq channel skills * feat: 更新remind工具投递目标地址格式 * feat: Refactor QQBot payload handling and improve code documentation - Simplified and clarified the structure of payload interfaces for Cron reminders and media messages. - Enhanced the parsing function to provide clearer error messages and improved validation. - Updated platform utility functions for better cross-platform compatibility and clearer documentation. - Improved text parsing utilities for better readability and consistency in emoji representation. - Optimized upload cache management with clearer comments and reduced redundancy. - Integrated QQBot plugin into the bundled channel plugins and updated metadata for installation. * OK apps/macos/Sources/OpenClaw/HostEnvSecurityPolicy.generated.swift > [email protected] check:bundled-channel-config-metadata /Users/yuehuali/code/PR/openclaw > node --import tsx scripts/generate-bundled-channel-config-metadata.ts --check [bundled-channel-config-metadata] stale generated output at src/config/bundled-channel-config-metadata.generated.ts ELIFECYCLE Command failed with exit code 1. ELIFECYCLE Command failed with exit code 1. * feat: 添加 QQBot 渠道配置及相关账户设置 * fix(qqbot): resolve 14 high-priority bugs from PR openclaw#52986 review DM routing (7 fixes): - #1: DM slash-command replies use sendDmMessage(guildId) instead of sendC2CMessage(senderId) - #2: DM qualifiedTarget uses qqbot:dm:${guildId} instead of qqbot:c2c:${senderId} - #3: sendTextChunks adds DM branch - #4: sendMarkdownReply adds DM branch for text and Base64 images - #5: parseAndSendMediaTags maps DM to targetType:dm + guildId - #6: sendTextToTarget DM branch uses sendDmMessage; MessageTarget adds guildId field - #7: handleImage/Audio/Video/FilePayload add DM branches Other high-priority fixes: - #8: Fix sendC2CVoiceMessage/sendGroupVoiceMessage parameter misalignment - #9: broadcastMessage uses groupOpenid instead of member_openid for group users - #10: Unify KnownUser storage - proactive.ts delegates to known-users.ts - #11: Remove invalid recordKnownUser calls for guild/DM users - #12: sendGroupMessage uses sendAndNotify to trigger onMessageSent hook - #13: sendPhoto channel unsupported returns error field - #14: sendTextAfterMedia adds channel and dm branches Type fixes: - DeliverEventContext adds guildId field - MediaTargetContext.targetType adds dm variant - sendPlainTextReply imgMediaTarget adds DM branch * fix(qqbot): resolve 2 blockers + 7 medium-priority bugs from PR openclaw#52986 review Blocker-1: Remove unused dmPolicy config knob - dmPolicy was declared in schema/types/plugin.json but never consumed at runtime - Removed from config-schema.ts, types.ts, and openclaw.plugin.json - allowFrom remains active (already wired into framework command-auth) Blocker-2: Gate sensitive slash commands with allowFrom authorization - SlashCommand interface adds requireAuth?: boolean - SlashCommandContext adds commandAuthorized: boolean - /bot-logs set to requireAuth: true (reads local log files) - matchSlashCommand rejects unauthorized senders for requireAuth commands - trySlashCommandOrEnqueue computes commandAuthorized from allowFrom config Medium-priority fixes: - #15: Strip non-HTTP/non-local markdown image tags to prevent path leakage - #16: applyQQBotAccountConfig clears clientSecret when setting clientSecretFile and vice versa - #17: getAdminMarkerFile sanitizes accountId to prevent path traversal - #18: URGENT_COMMANDS uses exact match instead of startsWith prefix match - #19: isCronExpression validates each token starts with a cron-valid character - #20: --token format validation rejects malformed input without colon separator - #21: resolveDefaultQQBotAccountId checks QQBOT_APP_ID environment variable * test(qqbot): add focused tests for slash command authorization path - Unauthorized sender rejected for /bot-logs (requireAuth: true) - Authorized sender allowed for /bot-logs - Non-requireAuth commands (/bot-ping, /bot-help, /bot-version) work for all senders - Unknown slash commands return null (passthrough) - Non-slash messages return null - Usage query (/bot-logs ?) also gated by auth check * fix(qqbot): align global TTS fallback with framework config resolution - Extract isGlobalTTSAvailable to utils/audio-convert.ts, mirroring core resolveTtsConfig logic: check auto !== 'off', fall back to legacy enabled boolean, default to off when neither is set. - Add pre-check in reply-dispatcher before calling globalTextToSpeech to avoid unnecessary TTS calls and noisy error logs when TTS is not configured. - Remove inline as any casts; use OpenClawConfig type throughout. - Refactor handleAudioPayload into flat early-return structure with unified send path (plugin TTS → global fallback → send). * fix(qqbot): break ESM circular dependency causing multi-account startup crash The bundled gateway chunk had a circular static import on the channel chunk (gateway -> outbound-deliver -> channel, while channel dynamically imports gateway). When two accounts start concurrently via Promise.all, the first dynamic import triggers module graph evaluation; the circular reference causes api exports (including runDiagnostics) to resolve as undefined before the module finishes evaluating. Fix: extract chunkText and TEXT_CHUNK_LIMIT from channel.ts into a new text-utils.ts leaf module. outbound-deliver.ts now imports from text-utils.ts, breaking the cycle. channel.ts re-exports for backward compatibility. * fix(qqbot): serialize gateway module import to prevent multi-account startup race When multiple accounts start concurrently via Promise.all, each calls await import('./gateway.js') independently. Due to ESM circular dependencies in the bundled output, the first import can resolve transitive exports as undefined before module evaluation completes. Fix: cache the dynamic import promise in a module-level variable so all concurrent startAccount calls share the same import, ensuring the gateway module is fully evaluated before any account uses it. * refactor(qqbot): remove startup greeting logic Remove getStartupGreetingPlan and related startup greeting delivery: - Delete startup-greeting.ts (greeting plan, marker persistence) - Delete admin-resolver.ts (admin resolution, greeting dispatch) - Remove startup greeting calls from gateway READY/RESUMED handlers - Remove isFirstReadyGlobal flag and adminCtx * fix(qqbot): skip octal escape decoding for Windows local paths Windows paths like C:\Users\1\file.txt contain backslash-digit sequences that were incorrectly matched as octal escape sequences and decoded, corrupting the file path. Detect Windows local paths (drive letter or UNC prefix) and skip the octal decoding step for them. * fix bot issue * feat: 支持 TTS 自动开关并清理配置中的 clientSecretFile * docs: 添加 QQBot 配置和消息处理的设计说明 * rebase * fix(qqbot): align slash-command auth with shared command-auth model Route requireAuth:true slash commands (e.g. /bot-logs) through the framework's api.registerCommand() so resolveCommandAuthorization() applies commands.allowFrom.qqbot precedence and qqbot: prefix normalization before any handler runs. - slash-commands.ts: registerCommand() now auto-routes by requireAuth into two maps (commands / frameworkCommands); getFrameworkCommands() exports the auth-required set for framework registration; bot-help lists both maps - index.ts: registerFull() iterates getFrameworkCommands() and calls api.registerCommand() for each; handler derives msgType from ctx.from, sends file attachments via sendDocument, supports multi-account via ctx.accountId - gateway.ts (inbound): replace raw allowFrom string comparison with qqbotPlugin.config.formatAllowFrom() to strip qqbot: prefix and uppercase before matching event.senderId - gateway.ts (pre-dispatch): remove stale auth computation; commandAuthorized is true (requireAuth:true commands never reach matchSlashCommand) - command-auth.test.ts: add regression tests for qqbot: prefix normalization in the inbound commandAuthorized computation - slash-commands.test.ts: update /bot-logs tests to expect null (command routed to framework, not in local registry) * rebase and solve conflict * fix(qqbot): preserve mixed env setup credentials --------- Co-authored-by: yuehuali <[email protected]> Co-authored-by: walli <[email protected]> Co-authored-by: WideLee <[email protected]> Co-authored-by: Frank Yang <[email protected]>
ronan-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 19, 2026
… mock (#176) Scaffolds the in-repo ZooKeeper coordination SDK per the plan on #175. PR 1 of 5. Ships (no recipes yet — that's PR 2): - src/plugin-sdk/zk.ts — public barrel; cheap contract types re-exported eagerly, createZkClient() as async factory that dynamically imports the heavy driver on first call - src/plugin-sdk/zk/client.ts — wrapDriver, subscribeState, dispatcher - src/plugin-sdk/zk/driver.ts — ZkDriver interface (ConnectionState, CreateMode, WatchEvent, ZkStat, driver factory signature) - src/plugin-sdk/zk/driver-native.ts — adapter over the `zookeeper` (yfinkelstein) native npm package, dynamic-imported only on first createZkClient({}) call so a cold `import` of the subpath never touches native - src/plugin-sdk/zk/driver-mock.ts — in-memory ZK respecting ephemeral/sequential + data/children watches + ephemeral cleanup on close/expire; exported so recipe tests (PR 2) can run without a running ensemble - src/plugin-sdk/zk/errors.ts — typed ZkError hierarchy with toZkError() normalizer driven by error.code unions (no instanceof-driven control flow required at call sites) - src/plugin-sdk/zk/paths.ts — pure helpers (joinPath, validatePath, featurePath) enforcing the /openclaw/<env>/<feature>/... convention - src/plugin-sdk/zk.test.ts — 10 tests against the mock driver Wired into the public SDK surface: - scripts/lib/plugin-sdk-entrypoints.json adds "zk" - package.json exports adds ./plugin-sdk/zk - package.json optionalDependencies adds zookeeper@^7.2.0 — lazy compile, tolerated at install time - src/config/types.zk.ts + types.openclaw.ts add the optional zk? field - src/config/zod-schema.ts adds ZkConfigSchema (strict, all-optional) so `openclaw config set zk.hosts …` won't bounce on strict validation - docs/plugins/zk.md — operator primer (setup, defaults, session lifecycle, quorum degradation) - docs/plugins/zk-parity.md — path-prefix convention table + pre-registered wire-up evidence template (per Cael's #4 + #5 review gaps, landed in the foundation PR so PR 5 can't hand-wave) - docs/.generated/plugin-sdk-api-baseline.sha256 regenerated for the new subpath Optional-dep tolerance smoke (clean-box Docker): $ docker run --rm node:22-slim bash -c ' mkdir /t && cd /t && cat > package.json <<EOF { "name":"smoke","optionalDependencies":{"zookeeper":"^7.2.0"} } EOF npm install --silent && echo RESULT=$?' RESULT=0 (zookeeper skipped — no build-essential; install succeeds anyway) Verification: - pnpm tsgo — clean - pnpm test src/plugin-sdk/zk.test.ts — 10/10 pass - pnpm build — emits dist/plugin-sdk/zk.js + dist/plugin-sdk/zk.d.ts; driver-native + client land as hashed dynamic-import chunks - pnpm check — clean - pnpm config:docs:check + pnpm plugin-sdk:api:check — hashes in sync Related: karmaterminal/openclaw-bootstrap#629 (merged, fleet_lock.py default fix closes bootstrap#623). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This was referenced Apr 19, 2026
karmafeast
added a commit
that referenced
this pull request
Apr 19, 2026
…dary tests Folds the load-bearing delta from #190 into #188 (which Silas noted at 19:05Z he'd close in favor of #188) plus the two coverage gaps Ronan self-flagged in his 19:09Z PR-author critical-eyes pass: 1. **Placement**: moved `continuationLine` from end-of-status (after `activationLine`) to immediately after `📚 ${contextLine}`. Continuation is context-adjacent signal — chain depth + pending/staged delegates live in the same operator-scan bucket as context pressure. Matches the semantics Silas's #190 landed on; makes the RFC §6.3 framing ("where the context banner lives") visually literal. 2. **Partial-row test**: chain + staged only (pending=0, volitional=0) should render `chain 2/10 | 1 post-compaction staged` with the zero fields omitted entirely. Addresses Ronan crit #4 (no partial-row coverage). 3. **Plural boundary test**: `1 delegate pending` (singular) vs the existing full-row case which uses `2 delegates pending` (plural). Addresses Ronan crit #5 (pluralization only covered in chain-only). Verification: - pnpm tsgo — clean - pnpm test src/auto-reply/status.test.ts — **61/61** (was 59/59) - oxlint — 0 warnings, 0 errors on both touched files Not changed (deliberately kept from #188 vs what #190 had): - **Extracted helper** `formatContinuationStatusLine` (testability > IIFE). - **Enabled-gate** returns `null` when `cfg.enabled !== true` — per RFC §6.3: "show only when `continuation.enabled === true` AND at least one field is non-zero". #190 omitted this gate; would display counters for a disabled-but-stale-counter session. Not what RFC specifies. - **Static ESM imports** (vitest can't reliably resolve lazy `require('./*.js')` from .ts source; eyeball-verified no cycle — neither `continuation/*` nor `request-compaction-tool.ts` imports `auto-reply/status`). Refs: #187, #190 (superseded per Silas's 19:05Z note).
ronan-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 20, 2026
… mock (#176) Scaffolds the in-repo ZooKeeper coordination SDK per the plan on #175. PR 1 of 5. Ships (no recipes yet — that's PR 2): - src/plugin-sdk/zk.ts — public barrel; cheap contract types re-exported eagerly, createZkClient() as async factory that dynamically imports the heavy driver on first call - src/plugin-sdk/zk/client.ts — wrapDriver, subscribeState, dispatcher - src/plugin-sdk/zk/driver.ts — ZkDriver interface (ConnectionState, CreateMode, WatchEvent, ZkStat, driver factory signature) - src/plugin-sdk/zk/driver-native.ts — adapter over the `zookeeper` (yfinkelstein) native npm package, dynamic-imported only on first createZkClient({}) call so a cold `import` of the subpath never touches native - src/plugin-sdk/zk/driver-mock.ts — in-memory ZK respecting ephemeral/sequential + data/children watches + ephemeral cleanup on close/expire; exported so recipe tests (PR 2) can run without a running ensemble - src/plugin-sdk/zk/errors.ts — typed ZkError hierarchy with toZkError() normalizer driven by error.code unions (no instanceof-driven control flow required at call sites) - src/plugin-sdk/zk/paths.ts — pure helpers (joinPath, validatePath, featurePath) enforcing the /openclaw/<env>/<feature>/... convention - src/plugin-sdk/zk.test.ts — 10 tests against the mock driver Wired into the public SDK surface: - scripts/lib/plugin-sdk-entrypoints.json adds "zk" - package.json exports adds ./plugin-sdk/zk - package.json optionalDependencies adds zookeeper@^7.2.0 — lazy compile, tolerated at install time - src/config/types.zk.ts + types.openclaw.ts add the optional zk? field - src/config/zod-schema.ts adds ZkConfigSchema (strict, all-optional) so `openclaw config set zk.hosts …` won't bounce on strict validation - docs/plugins/zk.md — operator primer (setup, defaults, session lifecycle, quorum degradation) - docs/plugins/zk-parity.md — path-prefix convention table + pre-registered wire-up evidence template (per Cael's #4 + #5 review gaps, landed in the foundation PR so PR 5 can't hand-wave) - docs/.generated/plugin-sdk-api-baseline.sha256 regenerated for the new subpath Optional-dep tolerance smoke (clean-box Docker): $ docker run --rm node:22-slim bash -c ' mkdir /t && cd /t && cat > package.json <<EOF { "name":"smoke","optionalDependencies":{"zookeeper":"^7.2.0"} } EOF npm install --silent && echo RESULT=$?' RESULT=0 (zookeeper skipped — no build-essential; install succeeds anyway) Verification: - pnpm tsgo — clean - pnpm test src/plugin-sdk/zk.test.ts — 10/10 pass - pnpm build — emits dist/plugin-sdk/zk.js + dist/plugin-sdk/zk.d.ts; driver-native + client land as hashed dynamic-import chunks - pnpm check — clean - pnpm config:docs:check + pnpm plugin-sdk:api:check — hashes in sync Related: karmaterminal/openclaw-bootstrap#629 (merged, fleet_lock.py default fix closes bootstrap#623). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This was referenced Apr 24, 2026
karmafeast
pushed a commit
that referenced
this pull request
Apr 24, 2026
Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean.
cael-dandelion-cult
added a commit
that referenced
this pull request
Apr 25, 2026
…2026.4.22 (#306) * feat(continuation): core implementation — context-pressure, request-compaction, post-compaction relay Cherry-picked from 1888edb onto v2026.4.22 with conflict resolution: - Fixed @sinclair/typebox → typebox imports (3 new tool files) - Merged upstream createReplyMediaContext rename with continuation additions - Preserved both upstream failedTerminalOutcome guard and continuation skipAnnounceDelivery - Kept upstream resolveNonNegativeNumber + retireRolledCronSessionMcpRuntime - Kept both upstream cleanupBundleMcpOnRunEnd and continuation fields in gateway schema/methods * docs(continuation): RFC continue-work-signal-v2 Add docs/design/continue-work-signal-v2.md — full RFC for the continuation surface (continue_work, continue_delegate, request_compaction), including trigger taxonomy (§4.1), context-pressure semantics (§4.2), compaction lifecycle (§4.3), post-compaction relay & rehydration (§4.4), and lifecycle hooks + platform settings (§4.5). Attribution-split commit per PR-PRESENTATION-RUNBOOK §4. Co-authored-by: Cael (cael-dandelion-cult) <[email protected]> Co-authored-by: dandelion cult - cael 🩸 <[email protected]> Co-authored-by: dandelion cult - ronan 🌊 <[email protected]> * test(continuation): coverage for context-pressure, post-compaction, taskflow durability Test surface for the continuation feature: agent-runner integration tests, context-pressure unit tests, post-compaction lifecycle event tests, TaskFlow delegate persistence tests, zod-schema continuation tests, gateway protocol agent-schema tests, and subagent-registry lifecycle tests. Attribution-split commit per PR-PRESENTATION-RUNBOOK §4. Co-authored-by: Cael (cael-dandelion-cult) <[email protected]> Co-authored-by: dandelion cult - cael 🩸 <[email protected]> Co-authored-by: dandelion cult - ronan 🌊 <[email protected]> * chore(continuation): generated baselines, i18n, build config, swift protocol, gitignore Integration/codegen surface for the continuation feature: - docs/.generated/{config-baseline,plugin-sdk-api-baseline}.sha256 — regenerated baselines - src/config/schema.base.generated.ts — regenerated base config schema (auto-generated) - docs/.i18n/glossary.zh-CN.json — i18n glossary additions for continuation terms - apps/macos + apps/shared OpenClawProtocol/GatewayModels.swift — Swift mirror of new gateway protocol fields - apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json — tool-display metadata for continuation tools - tsdown.config.ts — build config for new continuation surface - .gitignore — coverage/artifact exclusions Attribution-split commit per PR-PRESENTATION-RUNBOOK §4. Co-authored-by: Cael (cael-dandelion-cult) <[email protected]> Co-authored-by: dandelion cult - cael 🩸 <[email protected]> Co-authored-by: dandelion cult - ronan 🌊 <[email protected]> Co-authored-by: ronan-dandelion-cult <[email protected]> * chore: remove spurious note.txt from cherry-pick * chore: regenerate config + plugin-sdk baselines after continuation merge Reflects new agents.defaults.continuation.* config keys added by the context-pressure / request-compaction / post-compaction-relay surface. * fix(continuation): purge generation-guard per RFC 2026-04-15 (#299) Surgical removal of the continuationGuardLog logger, generation-drift check in timer callbacks, generationGuardTolerance config param, and session-entry continuation-state cleanup that the continue-work-signal-v2 RFC retired on 2026-04-15 (PR #299). The attribution-split/v2026.4.21-feature-only basis used for silas/rebase/v2026.4.22-feature was cut BEFORE #299 landed, so the guard survived the rebase. This commit lands the equivalent removal on top of the rebased branch via hand-edit (the canary b6c6f3b cannot be cherry-picked cleanly due to lich-protocol surface drift). Caught by Cael 🩸 at 09:23 PDT 2026-04-23 during second-angle compare- notes pass — exact failure mode figs warned about: "lose entire chunks of feature" via wrong tag→branch basis. Verification gates after this commit: - grep -rn 'continuationGuardLog' src/ → 0 - grep -rn 'continuation/guard' src/ → 0 - grep -rn 'generationGuardTolerance' src/ → 0 - pnpm tsgo + pnpm build clean - continuation test suite green Co-Authored-By: Claude Opus 4.6 <[email protected]> * docs(continuation): absorb Trigger F into RFC §4.1 taxonomy Trigger F is the in-turn pressure-fire emission anchor that the existing overflow (A) and timeout-recovery (B) paths emit from src/agents/pi-embedded-runner/run.ts. Code + 2 regression-guard tests (run.overflow-compaction.loop.test.ts:96, run.timeout-triggered-compaction.test.ts:105) already cite 'trigger F, per RFC §4.1' but the taxonomy table only defined A–E. Adds row F as a convergent emission of A+B (not a new decision path), with code anchors and rationale (single grep across [context-pressure:fire] surfaces both pre-run D and in-turn F). Per Cael 🩸's review-fleet finding (silas/swim-36/C1) + Silas 🌫️ recommendation: absorb into RFC, don't strip from code. Real working surface, just unnamed. * fix(continuation): pre-merge minors from 2026-04-23 review fleet Two findings cross-confirmed by claude-opus + codex + copilot reviewers plus prince syntheses (cael/silas/ronan) on staging tip 8b9444a: 1. request_compaction tool description still claimed 'no new messages may have arrived since your turn started' — generation guard was removed 2026-04-15 by RFC (§4.3). The lane queue already serializes compaction relative to subsequent messages, so the prose-claim was describing a guard that no longer exists. Fixed in both the JSDoc block and the user-visible tool description. 2. Bracket-path chain-cap and cost-cap rejections in agent-runner.ts (lines 2436-2456 surface) silently dropped continuation requests with no enqueueSystemEvent. The tool-delegate path at :2673-2692 correctly notifies. Restored symmetry — bracket-path now emits a [continuation] system event on chain-cap and cost-cap rejection so the agent learns its bracket request was dropped, matching the tool-delegate path's behavior. No behavior change to the success path. tsgo clean. * fix(continuation): plumb provider+model into volitional compaction call Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean. * docs(continuation): annotate RFC §6.1 equivalent-idiom for band-dedup sentinel Cohort D1 walk on 2026-04-24 (🩸 + 🌊 + 🌻 + 🌫️ converged): the existing context-pressure.ts:65 shape — `band === 0 || band === (lastBand ?? 0)` — is observably equivalent to the RFC §6.1-described `-1` missing-key sentinel under all Zod-valid configs. The schema enforces `contextPressureThreshold ≥ 0.005` → `thresholdPct ≥ 1`, which makes `band === 0` unreachable from valid configs, so the `?? 0` collision path the RFC fix targets cannot be triggered. Both shapes ship the same first-crossing semantics; the `-1` form is narratively cleaner expression of the invariant, the `?? 0 + band === 0 short-circuit` is the schema-bounded equivalent already in the code. Banked as the third classification: equivalent-idiom (neither bug nor doc-drift, prose-vs-code idiom mismatch). One-line annotation in §6.1 documents the equivalence so future reviewers don't re-walk to a 'BLOCKER' verdict against equivalent-behavior code. No code change. Doc-only. * docs(continuation): fix broken Swim 7 evidence links in RFC Swim 7 evidence was historically re-homed to perma-branch silas/swim7-runtime-evidence; the in-tree relative paths in §D.2 and §D.3 still pointed to ./continue-work-signal-v2/swim-evidence/ swim-07/SWIM7-RESULTS.md which doesn't exist on this branch. Rewrite both refs to the perma-branch URL. SWIM7-RESULTS.md is named in the description so readers know what artifact to look for on the branch. Fixes check-docs CI failure on PR #306. * fix(continuation): gate continue_delegate on drain + sync display detailKeys Address Copilot review feedback on PR #306: 1. createContinueDelegateTool was registered whenever continuation was enabled, exposing it on runs that do NOT drain the continuation delegate queue (e.g. llm-slug-generator explicitly sets drainsContinuationDelegateQueue: false). Delegates enqueued from such runs would never dispatch -> memory growth + confusing UX. Now gated on drainsContinuationDelegateQueue === true alongside the existing continuation-enabled check, matching the policy createRequestCompactionTool already follows for its own requestCompactionOpts gate. 2. tool-display-config.ts detailKeys for the continuation tools did not match the actual tool input schema: - continue_delegate listed fireAfterMs but the tool schema uses delaySeconds (the bracket parser uses delayMs separately). - continue_work omitted delaySeconds entirely, hiding the requested delay from tool-call summaries. Fixed both detailKeys to use delaySeconds and regenerated the apps/shared/OpenClawKit/Sources/OpenClawKit/Resources/tool-display.json baseline via pnpm tool-display:write. Co-Authored-By: Silas Solidor <[email protected]> Co-Authored-By: Cael Solidor <[email protected]> Co-Authored-By: Elliott Solidor <[email protected]> * fix(continuation): default-allow continue_delegate; only block explicit non-drainers Test failure on c825009: continuation-tools-registration.test.ts expects continue_delegate present on normal turns (where drainsContinuationDelegateQueue is undefined). Prior fix used `=== true` which excluded the undefined-default case. Inverted gate to `!== false` so: - undefined (normal turns / default callers) → tool present - true (explicit drainers like main session) → tool present - false (llm-slug-generator and other explicit non-drainers) → tool absent This matches the original Copilot nit's intent: only the *explicit* non-draining runs should hide the tool; everything else should keep the prior default-on behavior. * test(continuation): pin drainsContinuationDelegateQueue truth-table Three new cases in continuation-tools-registration.test.ts pinning the gate predicate's three states so a future refactor cannot silently regress to === true (which broke normal turns on c825009 before 9f00132 inverted to !== false): - undefined → continue_delegate present (default normal turns) - true → continue_delegate present (explicit drainers) - false → continue_delegate absent (e.g. llm-slug-generator) Local: all 6 tests pass under vitest.agents.config.ts in ~30s. Codifies the muscle that should have caught the c825009 regression pre-push. Co-authored-by: Elliott 🌻 <[email protected]> Co-authored-by: Silas 🌫️ <[email protected]> Co-authored-by: Cael 🩸 <[email protected]> * fix(types): tighten continuationTriggerOverride to ContinuationTrigger union Codex review (#306 r3140391334, r3140391373) flagged that `continuationTriggerOverride` was typed as plain `string` while the gateway schema restricts `continuationTrigger` to the closed union `ContinuationTrigger = "work-wake" | "delegate-return"` (src/auto-reply/get-reply-options.types.ts:6). Tightening the 4 declaration sites to use the union catches typos at compile time instead of letting them reach the gateway and reject at runtime. Pass-through assignments stay correct (callers were already passing valid values; the only call site src/agents/subagent-announce.ts :995 passes `delegateReturnTrigger: "delegate-return" | undefined`, which is in the union). Sites updated: - src/agents/subagent-announce-queue.ts:24 - src/agents/subagent-announce-delivery.ts:375, 450, 601 Verification: - pnpm exec tsc --noEmit: clean - pnpm exec vitest run --config test/vitest/vitest.agents.config.ts subagent-announce: 69/69 pass --------- Co-authored-by: Cael 🩸 <[email protected]> Co-authored-by: Ronan 🌊 <[email protected]> Co-authored-by: dandelion cult - cael 🩸 <[email protected]> Co-authored-by: dandelion cult - ronan 🌊 <[email protected]> Co-authored-by: Elliott 🌻 <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Silas Solidor <[email protected]> Co-authored-by: Cael Solidor <[email protected]> Co-authored-by: Elliott Solidor <[email protected]> Co-authored-by: dandelion cult - silas🌫️ <[email protected]>
ronan-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 25, 2026
Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean.
ronan-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 25, 2026
Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean.
ronan-dandelion-cult
pushed a commit
that referenced
this pull request
Apr 26, 2026
Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean.
silas-dandelion-cult
added a commit
that referenced
this pull request
Apr 27, 2026
…lace with reservation-missing test 🌊 (msg 1498380176445407274) flagged proposed test #5 (wake-then-cap) should drop along with Q4 deferral. Replaced with reservation-missing test (Q7's actual current-seam wire). Now all 5 tests are pure observability of existing behavior; zero new gate behavior. Refs #334
elliott-dandelion-cult
added a commit
that referenced
this pull request
Apr 27, 2026
… memo (#386) * docs(continuation): #334 chunk 5b — design memo for continuation.delegate.fire seam Memo-before-wire per cohort cooling-step request (🩸, 2026-04-27). Six design questions for cohort review: Q1: Sites — timer-callback only (proposal), not immediate dispatch Q2: Attr shape — reuse ContinuationSpanAttrs + 1 optional fire.deferred_ms Q3: Helper signature — mirrors chunks 2/3/4 contract Q4: Wake-then-cap — two spans, not composite (per 🌻's verb-on-timer / verb-on-gate framing) Q5: WORK-fire symmetry — punt to chunk 5c Q6: Fire-time exceptions — emit fire-span first, then sibling for error Refs #334 * docs(continuation): #334 chunk 5b memo — fold cohort review (Q2/Q3/Q4/Q7) Cohort review on memo PR #386: - 🩸 (msg 1498377749499351203): pin chainStepRemaining as dispatch-snapshot (not fire-time recompute); name plainly in JSDoc. - 🌊 (msg 1498377809591013516): canonical drift formula (drift = fire.deferred_ms − delay.ms); chain.id closure-not-reread; fire-time cap-recheck axes = cap.chain | cap.cost only (per-turn settled at dispatch, doesn't re-gate at fire). - 🌊 (msg 1498377810383998996): Q7 — reservation-missing at fire-time; emit fire + continuation.disabled (reason = reservation.missing); extend disabled.reason enum to 4-value for 5b; skip-family split deferred to 5c+ if non-cap reasons proliferate. Helper sig now: chainStepRemainingAtDispatch (named for clarity), chainId closed-over (no activeSessionEntry re-read at fire-time). Open-questions section replaced with banked-decisions section; ready for memo approval and wire PR follow-up. Refs #334 * docs(continuation): #334 chunk 5b memo — fold 🌻 review (Q2 integer ms, Q3 always-defined, Q8 deferred) 🌻 review (msg 1498377947944456294): - Q2: integer ms (Math.floor) for fire.deferred_ms; matches delay.ms shape - Q3: pin chainId always-defined invariant in JSDoc; defense-in-depth no-op fallback if undefined slips through - Q8 (deferred, not 5b-blocking): continuation.delegate.error as future home for non-cap state-mismatch failures (parent gone, session evicted). Banked; reservation.missing stays on continuation.disabled for 5b per Q7's (i-a); migration path opens in 5c+ memo if non-cap reasons proliferate. Refs #334 * docs(continuation): #334 chunk 5b memo — Q3 dedicated snapshot-vs-live JSDoc paragraph 🌻 follow-up (msg 1498378054462869524) on 🩸's snapshot-vs-live catch: upgrade the parenthetical to a dedicated JSDoc paragraph in the wire PR helper. Captures: (1) dispatch-time headroom (snapshot), not callback-time live state; (2) rationale = trace continuity with dispatch span; (3) future fire-time-headroom axis would be a separate field (provisional chain.step.remaining_at_fire), not a fold. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 reframed (i-a) vs (i-b), 🌫️ shifts to (i-b) 🌻 (msg 1498378164936638464) raises the family-tightness argument: adding reservation.missing to disabled.reason widens the enum from a tight cap-gate family (cap.*) into a mixed 'any-reason-this-was-disabled' axis, eroding what 🩸 fought for in chunk 4. Proposes (i-b): new sibling span continuation.delegate.dropped for non-cap fire-time no-ops, keeping continuation.disabled strictly cap-family. 🌫️ shifts to (i-b). 🩸's 'don't overload cap-axes' (msg 1498377931469099119) + 🌻's 'don't widen the family' are the same observation from two angles. New span name is cheap; family-coherence is structural. Q7 status now ⏳ awaiting cohort vote (i-a / i-b / ii) before wire. Banked-decisions Q7 line item updated. Q8 updated: continuation.delegate.error may be distinct from .dropped (hard-fault vs soft-drop) — 5c+ memo can disambiguate. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 RESOLVED (i-a); cohort sweep 4/4 🌻 fold (msg 1498378311259394158) on 🩸's grammar argument: 'fire = verb on timer; disabled = verb on gate / prevented follow-through' generalizes — gate is cap OR reservation-loss, same grammar. Family-fit > family-tightness; the enum becomes 'anything that prevented follow-through' not 'cap axes only.' 🌫️ re-folds to (i-a) on cohort sweep + grammar weight. (i-b) lean was correct on the structural worry but wrong on weight — splitting into .dropped would fragment semantically-identical events across two span names. Cohort sweep on Q7: 🌊 (i), 🩸 (i-a), 🌻 fold to (i-a), 🌫️ fold to (i-a). 4/4 on (i-a). JSDoc requirement on disabled.reason enum: pin semantics as 'anything that prevented follow-through' not 'cap axes only.' Future siblings (reservation.evicted, session.gone, compaction.cleared) live on the same family. Q8 narrowed: continuation.delegate.error reserved for HARD-FAULT failures (uncaught exception, store write fail), distinct from soft-prevented gates which all land on continuation.disabled. Refs #334 * docs(continuation): #334 chunk 5b memo — 🩸 byte-walk corrects Q4, confirms Q3, reaffirms Q7 🩸 byte-walk on trunk (msgs 1498379202557382806 + 1498379203257569481): CORRECTIONS: - Q4 reframed: fire-time cap re-checks are NOT current behavior. Today timer-callback path is exactly: 1. takeDelayedContinuationReservation() 2. if missing → log + return 3. otherwise → doSpawn() Wake-then-cap is a FUTURE policy seam, not status-quo instrumentation. 5b deferred from this seam; future memo can introduce gate at fire if policy demands. 🌫️'s earlier 'fire-time cap-recheck axes = cap.chain | cap.cost only' was forward-looking design opinion misread as status-quo description. CONFIRMATIONS: - Q3: persistedChainIdForTimer already binds enqueue-time at L2330-L2358 (bracket) and L2687-L2713 (tool). Closure-local provenance is the status quo; no reread, no mint. - Q7: reservation-missing IS the actual existing fire-time divergence case in current bytes. Current behavior is log-and-return; 5b extends to fire+disabled instrumentation as INTENTIONAL EXTENSION (not just observation of existing emission). Q7 cohort scoring after byte-walk + 🌊 retraction: - 🌊 (i-a, retracted i-b in msg 1498379091165053058) - 🩸 byte-walk read confirms (i-a)-compatible - 🌻 holds (i-b) on principled verb-grammar-completion (msg 1498379005857103902) 2-1 (i-a). 🌻's principled dissent banked for future taxonomy refactor. New 'Status quo vs future policy' section added explicitly to scope 5b to instrumentation-only and prevent accidental policy widening. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 unanimous (3/3) + 🩸 wire-framing caveat 🌻 fold confirmed unanimous (i-a) in msg 1498379486138470563. Q7 cohort sweep updated to 3/3 with 🌊's verb-on-gate grammar umbrella as the unifying frame. 🩸's wire-framing caveat (msg 1498379735493775560) added as dedicated section: wire PR description must carry the existing-seam vs new-policy distinction plainly. Specifically: - reservation.missing IS a real current callback seam in the bytes; 5b extends log-and-return to fire+disabled instrumentation - fire-time cap.chain | cap.cost rechecks are a future policy shape that may be added later, NOT current behavior - enum extension to 4-value is structural (future-policy home), but 5b's emission sites stay narrow: dispatch-time gate-rejects (existing) + fire-time reservation-missing (new). No fire-time cap-emission. Refs #334 * docs(continuation): #334 chunk 5b memo — per-Q scope axis (current seam vs future policy) 🌻 ask (msg 1498379793954246676): tag each Q by 🩸's current-seam vs future-policy axis so 5b stays honest as instrumentation-of-status-quo. New table at top of decisions section: - Q1, Q2, Q3, Q6, Q7 → current seam (instrument what exists) - Q4 wake-then-cap, Q5 work-fire → future policy (out of 5b) - Q8 delegate.error → future taxonomy (deferred) Final 5b wire sites pinned narrow: - continuation.delegate.fire at timer-callback start - continuation.disabled (reason=reservation.missing) on log-and-return path - existing dispatch-time gate-rejects unchanged 5b explicitly does NOT: - add fire-time cap.chain | cap.cost rechecks - add work-fire instrumentation - introduce continuation.delegate.error span name Diff estimate revised: ~80-100 lines (was ~180), reflecting narrower wire. Refs #334 * docs(continuation): #334 chunk 5b memo — drop wake-then-cap test, replace with reservation-missing test 🌊 (msg 1498380176445407274) flagged proposed test #5 (wake-then-cap) should drop along with Q4 deferral. Replaced with reservation-missing test (Q7's actual current-seam wire). Now all 5 tests are pure observability of existing behavior; zero new gate behavior. Refs #334 * docs(continuation): #334 chunk 5b memo — delete Q4 legacy block, oxfmt fix 🩸's blocker (msg 1498383014764482824 + 1498383401344962753 + 1498383905630195916): the 'Q4 legacy framing' subsection still contained present-tense **Wire:** prose that read as live 5b spec to anyone scanning section headings, despite the 'kept for receipts only — not 5b spec' header. The corrected Q4 above already preserves the design-direction (two-spans-not-composite, cap.chain|cap.cost axes if fire-time gating is ever wired) — the legacy block is now spec ambiguity, not a receipt. Delete the entire 'Q4 legacy framing' subsection (lines 112-130 at 3a9e65f). Corrected Q4 + Resolution for 5b above is unchanged and load-bearing. Plus: oxfmt --write on the memo file to clear check-docs CI red. Cohort 3/3 unanimous (i-a) on Q7. Memo settled. Wire PR follows. Co-authored-by: Silas (dandelion cult) <[email protected]> --------- Co-authored-by: Elliott (dandelion cult) <[email protected]>
Open
5 tasks
karmafeast
pushed a commit
that referenced
this pull request
May 1, 2026
Two findings from Ronan 🌊's PR-review fleet (#5 + #6): #5 (real bug): triggerCompaction closure at agent-runner-execution.ts:1101 called compactEmbeddedPiSession() without passing provider/model. Both fields are declared optional on CompactEmbeddedPiSessionParams (see compact.types.ts:42-43) and fall back to DEFAULT_PROVIDER/DEFAULT_MODEL inside compact.queued.ts when missing. For sessions running on a non-default model (every prince box: copilot/claude-opus-4.7), this silently dropped the active model on volitional request_compaction calls — the compaction would run on the default model rather than the session's model. Plumbed params.followupRun.run.provider and .model into the call (already in scope at line 704 in the same file). Closes the third defect from openclaw#191 that did not land in the squash basis. #6 (docstring tighten): agent-runner.ts:1573 said the context-pressure check runs 'before the agent turn' but it runs after setPhase('running'). The check IS before the actual provider request, just not before the phase-tracking flag flip. Tightened comment to say 'before the agent's model call' + named the setPhase-then-check ordering rationale. No behavior change. tsgo clean. (cherry picked from commit 5c9a924)
karmafeast
pushed a commit
that referenced
this pull request
May 1, 2026
… memo (#386) * docs(continuation): #334 chunk 5b — design memo for continuation.delegate.fire seam Memo-before-wire per cohort cooling-step request (🩸, 2026-04-27). Six design questions for cohort review: Q1: Sites — timer-callback only (proposal), not immediate dispatch Q2: Attr shape — reuse ContinuationSpanAttrs + 1 optional fire.deferred_ms Q3: Helper signature — mirrors chunks 2/3/4 contract Q4: Wake-then-cap — two spans, not composite (per 🌻's verb-on-timer / verb-on-gate framing) Q5: WORK-fire symmetry — punt to chunk 5c Q6: Fire-time exceptions — emit fire-span first, then sibling for error Refs #334 * docs(continuation): #334 chunk 5b memo — fold cohort review (Q2/Q3/Q4/Q7) Cohort review on memo PR #386: - 🩸 (msg 1498377749499351203): pin chainStepRemaining as dispatch-snapshot (not fire-time recompute); name plainly in JSDoc. - 🌊 (msg 1498377809591013516): canonical drift formula (drift = fire.deferred_ms − delay.ms); chain.id closure-not-reread; fire-time cap-recheck axes = cap.chain | cap.cost only (per-turn settled at dispatch, doesn't re-gate at fire). - 🌊 (msg 1498377810383998996): Q7 — reservation-missing at fire-time; emit fire + continuation.disabled (reason = reservation.missing); extend disabled.reason enum to 4-value for 5b; skip-family split deferred to 5c+ if non-cap reasons proliferate. Helper sig now: chainStepRemainingAtDispatch (named for clarity), chainId closed-over (no activeSessionEntry re-read at fire-time). Open-questions section replaced with banked-decisions section; ready for memo approval and wire PR follow-up. Refs #334 * docs(continuation): #334 chunk 5b memo — fold 🌻 review (Q2 integer ms, Q3 always-defined, Q8 deferred) 🌻 review (msg 1498377947944456294): - Q2: integer ms (Math.floor) for fire.deferred_ms; matches delay.ms shape - Q3: pin chainId always-defined invariant in JSDoc; defense-in-depth no-op fallback if undefined slips through - Q8 (deferred, not 5b-blocking): continuation.delegate.error as future home for non-cap state-mismatch failures (parent gone, session evicted). Banked; reservation.missing stays on continuation.disabled for 5b per Q7's (i-a); migration path opens in 5c+ memo if non-cap reasons proliferate. Refs #334 * docs(continuation): #334 chunk 5b memo — Q3 dedicated snapshot-vs-live JSDoc paragraph 🌻 follow-up (msg 1498378054462869524) on 🩸's snapshot-vs-live catch: upgrade the parenthetical to a dedicated JSDoc paragraph in the wire PR helper. Captures: (1) dispatch-time headroom (snapshot), not callback-time live state; (2) rationale = trace continuity with dispatch span; (3) future fire-time-headroom axis would be a separate field (provisional chain.step.remaining_at_fire), not a fold. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 reframed (i-a) vs (i-b), 🌫️ shifts to (i-b) 🌻 (msg 1498378164936638464) raises the family-tightness argument: adding reservation.missing to disabled.reason widens the enum from a tight cap-gate family (cap.*) into a mixed 'any-reason-this-was-disabled' axis, eroding what 🩸 fought for in chunk 4. Proposes (i-b): new sibling span continuation.delegate.dropped for non-cap fire-time no-ops, keeping continuation.disabled strictly cap-family. 🌫️ shifts to (i-b). 🩸's 'don't overload cap-axes' (msg 1498377931469099119) + 🌻's 'don't widen the family' are the same observation from two angles. New span name is cheap; family-coherence is structural. Q7 status now ⏳ awaiting cohort vote (i-a / i-b / ii) before wire. Banked-decisions Q7 line item updated. Q8 updated: continuation.delegate.error may be distinct from .dropped (hard-fault vs soft-drop) — 5c+ memo can disambiguate. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 RESOLVED (i-a); cohort sweep 4/4 🌻 fold (msg 1498378311259394158) on 🩸's grammar argument: 'fire = verb on timer; disabled = verb on gate / prevented follow-through' generalizes — gate is cap OR reservation-loss, same grammar. Family-fit > family-tightness; the enum becomes 'anything that prevented follow-through' not 'cap axes only.' 🌫️ re-folds to (i-a) on cohort sweep + grammar weight. (i-b) lean was correct on the structural worry but wrong on weight — splitting into .dropped would fragment semantically-identical events across two span names. Cohort sweep on Q7: 🌊 (i), 🩸 (i-a), 🌻 fold to (i-a), 🌫️ fold to (i-a). 4/4 on (i-a). JSDoc requirement on disabled.reason enum: pin semantics as 'anything that prevented follow-through' not 'cap axes only.' Future siblings (reservation.evicted, session.gone, compaction.cleared) live on the same family. Q8 narrowed: continuation.delegate.error reserved for HARD-FAULT failures (uncaught exception, store write fail), distinct from soft-prevented gates which all land on continuation.disabled. Refs #334 * docs(continuation): #334 chunk 5b memo — 🩸 byte-walk corrects Q4, confirms Q3, reaffirms Q7 🩸 byte-walk on trunk (msgs 1498379202557382806 + 1498379203257569481): CORRECTIONS: - Q4 reframed: fire-time cap re-checks are NOT current behavior. Today timer-callback path is exactly: 1. takeDelayedContinuationReservation() 2. if missing → log + return 3. otherwise → doSpawn() Wake-then-cap is a FUTURE policy seam, not status-quo instrumentation. 5b deferred from this seam; future memo can introduce gate at fire if policy demands. 🌫️'s earlier 'fire-time cap-recheck axes = cap.chain | cap.cost only' was forward-looking design opinion misread as status-quo description. CONFIRMATIONS: - Q3: persistedChainIdForTimer already binds enqueue-time at L2330-L2358 (bracket) and L2687-L2713 (tool). Closure-local provenance is the status quo; no reread, no mint. - Q7: reservation-missing IS the actual existing fire-time divergence case in current bytes. Current behavior is log-and-return; 5b extends to fire+disabled instrumentation as INTENTIONAL EXTENSION (not just observation of existing emission). Q7 cohort scoring after byte-walk + 🌊 retraction: - 🌊 (i-a, retracted i-b in msg 1498379091165053058) - 🩸 byte-walk read confirms (i-a)-compatible - 🌻 holds (i-b) on principled verb-grammar-completion (msg 1498379005857103902) 2-1 (i-a). 🌻's principled dissent banked for future taxonomy refactor. New 'Status quo vs future policy' section added explicitly to scope 5b to instrumentation-only and prevent accidental policy widening. Refs #334 * docs(continuation): #334 chunk 5b memo — Q7 unanimous (3/3) + 🩸 wire-framing caveat 🌻 fold confirmed unanimous (i-a) in msg 1498379486138470563. Q7 cohort sweep updated to 3/3 with 🌊's verb-on-gate grammar umbrella as the unifying frame. 🩸's wire-framing caveat (msg 1498379735493775560) added as dedicated section: wire PR description must carry the existing-seam vs new-policy distinction plainly. Specifically: - reservation.missing IS a real current callback seam in the bytes; 5b extends log-and-return to fire+disabled instrumentation - fire-time cap.chain | cap.cost rechecks are a future policy shape that may be added later, NOT current behavior - enum extension to 4-value is structural (future-policy home), but 5b's emission sites stay narrow: dispatch-time gate-rejects (existing) + fire-time reservation-missing (new). No fire-time cap-emission. Refs #334 * docs(continuation): #334 chunk 5b memo — per-Q scope axis (current seam vs future policy) 🌻 ask (msg 1498379793954246676): tag each Q by 🩸's current-seam vs future-policy axis so 5b stays honest as instrumentation-of-status-quo. New table at top of decisions section: - Q1, Q2, Q3, Q6, Q7 → current seam (instrument what exists) - Q4 wake-then-cap, Q5 work-fire → future policy (out of 5b) - Q8 delegate.error → future taxonomy (deferred) Final 5b wire sites pinned narrow: - continuation.delegate.fire at timer-callback start - continuation.disabled (reason=reservation.missing) on log-and-return path - existing dispatch-time gate-rejects unchanged 5b explicitly does NOT: - add fire-time cap.chain | cap.cost rechecks - add work-fire instrumentation - introduce continuation.delegate.error span name Diff estimate revised: ~80-100 lines (was ~180), reflecting narrower wire. Refs #334 * docs(continuation): #334 chunk 5b memo — drop wake-then-cap test, replace with reservation-missing test 🌊 (msg 1498380176445407274) flagged proposed test #5 (wake-then-cap) should drop along with Q4 deferral. Replaced with reservation-missing test (Q7's actual current-seam wire). Now all 5 tests are pure observability of existing behavior; zero new gate behavior. Refs #334 * docs(continuation): #334 chunk 5b memo — delete Q4 legacy block, oxfmt fix 🩸's blocker (msg 1498383014764482824 + 1498383401344962753 + 1498383905630195916): the 'Q4 legacy framing' subsection still contained present-tense **Wire:** prose that read as live 5b spec to anyone scanning section headings, despite the 'kept for receipts only — not 5b spec' header. The corrected Q4 above already preserves the design-direction (two-spans-not-composite, cap.chain|cap.cost axes if fire-time gating is ever wired) — the legacy block is now spec ambiguity, not a receipt. Delete the entire 'Q4 legacy framing' subsection (lines 112-130 at 3a9e65f). Corrected Q4 + Resolution for 5b above is unchanged and load-bearing. Plus: oxfmt --write on the memo file to clear check-docs CI red. Cohort 3/3 unanimous (i-a) on Q7. Memo settled. Wire PR follows. Co-authored-by: Silas (dandelion cult) <[email protected]> --------- Co-authored-by: Elliott (dandelion cult) <[email protected]>
This was referenced May 3, 2026
karmafeast
pushed a commit
that referenced
this pull request
May 28, 2026
…penclaw#76262) * fix(msteams): rebase SDK migration onto current main Reapply the msteams SDK migration (originally on feat/msteams-sdk-migration) on top of upstream/main, resolving conflicts with parallel msteams work that landed upstream during our session. What got applied vs decisions made: CLEANLY APPLIED (3-way patch): - monitor.ts, monitor-handler.ts, polls.ts, reply-stream-controller.ts/.test.ts, reply-dispatcher.ts, attachments/download.ts, monitor.lifecycle.test.ts, monitor-handler/message-handler.ts, monitor-handler.types.ts, etc. - streaming-message.ts + .test.ts deletions WHOLESALE TAKE FROM ORIGINAL BRANCH (partial 3-way left broken cross-refs): - sdk.ts, sdk.test.ts, messenger.ts, feedback-reflection.ts, send-context.ts, send.test.ts KEPT UPSTREAM (deferred for separate cleanup): - extensions/msteams/package.json (still has jsonwebtoken/jwks-rsa per Peter's b3bc60a incremental approach) - src/plugins/contracts/package-manifest.contract.test.ts (consistent with package.json) - pnpm-lock.yaml (avoids lockfile churn; pnpm install --frozen-lockfile clean) ADAPTED: - Dockerfile matrix-sdk-crypto check now wraps upstream's new retry-loop in the if-matrix-bundled gate KNOWN TEST FAILURES (need eyes — see PR comment): - attachments.test.ts: 1 fail (pre-existing — warn meta arg shape changed in our migration but test wasn't updated) - reply-dispatcher.test.ts: 6 fails (pre-existing — tests mock old TeamsHttpStream, not updated for our ctx.stream rewrite) - send.test.ts: 4 fails (NEW from merge — upstream's send.ts changed media loading; our mocks need updating or take upstream's send.test.ts wholesale) UPSTREAM COMMITS POTENTIALLY MISSED (in wholesale-take files): - 08c4af0 fix(msteams): accept conversation id allowlists - e1840b8 fix(msteams): bind global audience tokens to app id - Channels turn-kernel refactor (ffe67e9 / 1ead1b2 / 9a9cd0c) — may be partially preserved in cleanly-patched files Static checks pass: pnpm check:changed is green (typecheck, lint, contract tests, import cycles, etc.). Manual testing required before merge. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(msteams): preserve thread routing for channel and group-chat replies - monitor.ts: adaptSdkContext now uses ctx.reply() for channel and groupChat conversations (so the SDK threads outbound activities to the inbound's replyToId/serviceUrl) and ctx.send() only for personal DMs (where reply()'s blockquote-prepend is ugly). - messenger.ts: sendProactively passes resolvedThreadId on the non-thread fallback path so channel @mentions that fall through outbound.ts -> send.ts still land in the original thread instead of top-level. Live-validated: channel @mention -> bot replies in thread, threaded reply -> bot replies in same thread, no top-level leakage. * fix(msteams): tag outbound SDK calls with OpenClaw User-Agent - user-agent.ts: add buildOpenClawUserAgentFragment() that returns just 'OpenClaw/<version>'. The SDK's Client.clone merges this with its own 'teams.ts[apps]/<sdk-version>' identifier — passing the full buildUserAgent() here would double-print the SDK token. - sdk.ts: pass the fragment via AppOptions.client.headers['User-Agent'] so the Teams backend can identify OpenClaw traffic for usage telemetry. Final UA looks like 'OpenClaw/<openclaw-version> teams.ts[apps]/<sdk-version>'. * fix(msteams): handle StreamCancelledError when user presses Stop mid-stream The new SDK throws StreamCancelledError synchronously from stream.emit/update when the user pressed Stop in Teams: Teams replies 403 to the next chunk update, the SDK flips _canceled, and any subsequent emit() throws. The old custom TeamsHttpStream either swallowed cancel or didn't expose this exception type, so the migration inherited an SDK behavior the original code didn't have to handle. Symptom on 2026-05-05: pressing Stop during a streaming reply caused an unhandled promise rejection that crashed the Node 24 process. Docker restarted the gateway about two minutes after each Stop click. Two related bugs surfaced once the crash was caught: the would-be block fallback re-delivered the full text as a second message (duplicate after Stop), and the typing-keepalive kept pulsing in Teams for the rest of the agent run because nothing told it to stop. reply-stream-controller.ts: - Wrap stream.update / stream.emit / stream.close in try/catch that swallows StreamCancelledError (matched by .name to dodge tsgo's SDK re-export resolution quirk). Latch a wasCanceled flag so subsequent calls short-circuit even if stream.canceled is stale. - preparePayload() returns undefined when the stream was canceled — the streamed prefix is already visible to the user, so dropping the payload prevents a duplicate block message from overriding the cancel intent. reply-dispatcher.ts: - Typing-keepalive gate now also checks streamController.wasCanceled() so typing pulses stop firing once Stop is observed. Otherwise the bot keeps pulsing for the rest of the (uncancellable) agent run. reply-stream-controller.test.ts: - 6 new regression tests cover: cancel-during-emit (the crash scenario), cancel-during-update, cancel-during-finalize, non-cancel error propagation, post-cancel inactivity, and dropped-payload-on-cancel. Live-validated: long streaming reply + Stop mid-stream -> stream freezes, no duplicate message, no zombie typing, container stays healthy. * fix(msteams): allow Bearer-token retry on Skype CDN attachment downloads Teams puts inline DM images and clipboard-pasted images on *.asm.skype.com URLs (e.g. us-api.asm.skype.com/v1/objects/<id>/views/imgo). The download path in attachments/download.ts already does a plain GET first and falls back to a Bearer-token retry on 401/403 — but the retry was gated on the URL being in DEFAULT_MEDIA_AUTH_HOST_ALLOWLIST. asm.skype.com hosts were in DEFAULT_MEDIA_HOST_ALLOWLIST (download permitted) but not in the auth-host list, so a 401 plain-GET response skipped the retry and surfaced as a missing image to the agent. Add asm.skype.com and ams.skype.com to the auth allowlist so openclaw attempts the Bearer-token retry consistently, matching how it treats the other CDN/Bot-Framework hosts already in the list. Note: this does not unblock all clipboard-pasted DM images — for at least some tenants asm.skype.com rejects the Bot Framework token (returns 401 even with auth). Routing those URLs through <serviceUrl>/v3/attachments/... the way openclaw#62219 already handles HTML-wrapped attachments is a separate follow-up. The +button 'Upload from this device' path works today because Teams generates an attachment with an HTML wrapper that triggers the existing BF v3 attachments fallback in monitor-handler/inbound-media.ts. * fix(msteams): align docker-compose msteams port default with plugin default The plugin defaults webhook.port to 3978 (the Bot Framework standard used in Microsoft samples) and listens on whatever the operator sets there. The docker-compose.yml port mapping was exposing ${OPENCLAW_MSTEAMS_PORT:-3000}:3000 which only works for operators who explicitly set webhook.port to 3000. Default-config users would have the plugin listening on 3978 inside the container while compose forwarded 3000, causing connection refused. Realign to ${OPENCLAW_MSTEAMS_PORT:-3978}:3978 so a default-config docker compose up Just Works with Teams. Operators wanting a custom port override both webhook.port in openclaw.json and OPENCLAW_MSTEAMS_PORT env var. * fix(msteams): post-rebase reconciliation with main Three follow-ups after rebasing the SDK migration onto current main: - reply-dispatcher.ts: rename createChannelReplyPipeline to its post-rebase identifier createChannelMessageReplyPipeline (the plugin-sdk barrel renamed it during the 1454-commit rebase window). - reply-dispatcher.ts: tighten the typing-keepalive onStartError signature to (err: unknown) to satisfy upstream's stricter type checks. - messenger.ts: drop the unconditional thread suffix on the bottom proactive fallback. The previous behavior threaded all top-level proactive sends when the stored ref had a threadId, which contradicts replyStyle='top-level' semantics (and breaks the new upstream test). Threading on the proactive path is preserved where it matters — the onRevoked branch within replyStyle==='thread' still passes resolvedThreadId, which is the original openclaw#55198 fix path. - attachments.test.ts: update the warn-call assertion to match the migration's inline message format (host=... error=...) — the structured meta object was being dropped by the logger formatter pre-migration. * feat(msteams): port streaming preview/progress features to ctx.stream While the SDK migration was open, upstream landed preview/progress/draft streaming features built on the OLD custom TeamsHttpStream class (which the migration deletes). This commit ports the user-visible parts of those features onto the new ctx.stream substrate so the migration doesn't lose ground: - pickInformativeStatusText: reads custom labels from msteams.streaming.progressDraft config via resolveChannelProgressDraftLabel. Falls back to the plugin-sdk default rotation. Pre-rebase used a hardcoded 4-string array. - streamMode resolution: "partial" (default, per-token streaming), "progress" (no tokens; preview card carries informative label that updates as tools run), or "block" (no native streaming). Mode is read from cfg.channels.msteams.streaming.preview. - progress-draft gate: createChannelProgressDraftGate gates informative updates so the rotating label only starts firing once meaningful work has begun (avoids flicker before the first tool call). - noteProgressWork() / pushProgressLine(): public methods on the controller for callers (typing keepalive ticks, tool-event callbacks) to signal work. pushProgressLine appends tool names as bullets above the rotating label when streaming.previewToolProgress is enabled. Wiring these into actual tool events is a separate follow-up. - preparePayload progress-mode path: when stream is active but no tokens streamed (progress mode) and a final text payload arrives, emit the text into the stream so the preview card transitions in place to the final reply on close(). reply-dispatcher: pass log + msteamsConfig + a stable progressSeed (${accountId}:${conversation.id}) to createTeamsReplyStreamController so the informative-label rotation is consistent across reconnects. What's NOT ported and why: - Live-edit-via-replaceInformativeWithFinal: the SDK's HttpStream natively accumulates emitted text + entities + channelData and flushes ONE final activity at close() using the same activity id as the preview. So the separate "replace informative with final" call from upstream is unnecessary — we get live-finalization for free via the SDK's design. - pushProgressLine triggers from tool events: needs reply-pipeline-side callbacks the new SDK migration didn't surface yet. Follow-up. Tests: existing 22 reply-stream-controller tests still pass (the new behaviors are additive). * feat(msteams): wire pipeline tool events to streaming progress + fix test debt Two follow-ups from yesterday's stopping point: 1. Wire pipeline events into the stream controller's progress-draft surface. reply-dispatcher's replyOptions now exposes onReasoningStream, onToolStart, onItemEvent, onPlanUpdate, onApprovalEvent, onCommandOutput callbacks that format each event via the channel-streaming helpers and route through streamController.pushProgressLine(). Mirrors the discord adapter's wiring. Also: - resolveChannelStreamingPreviewToolProgress + ...SuppressDefaultTool... so the dispatcher exposes suppressDefaultToolProgressMessages on its replyOptions when progress mode is on. - Switch disableBlockStreaming resolution to the channel-streaming helpers (resolveChannelPreviewStreamMode + resolveChannelStreamingBlockEnabled) so streaming.mode='block' and streaming.block.enabled=true are honored alongside the legacy blockStreaming boolean. 2. Fix the test debt that the rebase exposed: - reply-dispatcher.test.ts: drop the streamInstances + TeamsHttpStream mock pattern (file deleted by migration); replace with a streamMock provided via context.stream that mirrors the SDK's IStreamer shape (update/emit/close/canceled). Update assertions on sendInformativeUpdate -> stream.update, stream.update -> stream.emit. Drop the resumes-typing-between-segments test (no equivalent in the new ctx.stream model — the SDK's HttpStream doesn't have a 'between segments' notion; close ends the stream). - send.test.ts: fix two stale mock targets — loadOutboundMediaFromUrl comes from openclaw/plugin-sdk/outbound-media (not /msteams), and resolveMarkdownTableMode comes from openclaw/plugin-sdk/markdown-table-runtime (not /config-runtime). The previous mock paths were no-ops post-migration. All 854 msteams tests now pass (was 17 failing in 4 files yesterday). * fix(msteams): SDK streaming delta + use app.reply for proactive thread sends Two narrow regressions exposed by the @microsoft/teams.apps migration: - The SDK's HttpStream.emit appends each chunk to its internal buffer (`this.text += activity.text`), but the channel reply pipeline emits cumulative text on each chunk. Forwarding cumulative text into an appending sink produced "chunk1 + chunk1chunk2 + chunk1chunk2chunk3..." duplication for streamed (DM) replies. Track the emitted prefix length in the stream controller and only forward the new tail. - Replace the manual `${convId};messageid=${msgId}` URL construction in the proactive thread fallback with `app.reply()`, which builds the threaded conversation id via the SDK's own toThreadedConversationId helper. Mechanically equivalent today; removes coupling to Teams' URL format and tracks any future SDK changes. Also adds the `reply` method to the structural MSTeamsApp type so the refactor typechecks without casts. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(msteams): bump @microsoft/teams.api and teams.apps to 2.0.10 2.0.10 adds support for the AAD v1 token issuer that the Bot Framework JWT validator needs. The minor version bump pulls teams.cards / common / graph along to 2.0.10 too. Add `@microsoft/teams.*` to `minimumReleaseAgeExclude` in pnpm-workspace.yaml because 2.0.10 was published <48h ago and the default `minimumReleaseAge: 2880` (~2 days) would otherwise reject it. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * revert(msteams): remove asm.skype.com auth-host allowlist additions These hosts were added in dfc169d for inline DM image auth-retry, but the commit's own footnote acknowledges it doesn't actually unblock clipboard-pasted images (asm.skype.com rejects Bot Framework tokens in at least some tenants). The change is unrelated to the SDK migration and the user-visible bug it claimed to fix isn't fixed; lifting it out keeps this PR focused on the migration. Will land as a separate PR if the auth-allowlist consistency improvement is wanted on its own. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * refactor(msteams): typed ExpressAdapter helper, drop unknown-cast pyramid The monitor's SDK bootstrap had an awkward chain: httpServerAdapter: new ( (await import("@microsoft/teams.apps")) as unknown as { ExpressAdapter: new (app: unknown) => unknown; } ).ExpressAdapter(expressApp) as never, Three casts (`unknown`, structural shape literal, `never`) were a defensive workaround from when the SDK's hashed d.ts files tripped up tsgo. With the SDK's exports now resolving cleanly, the same import can be done with full types. - Extend the lazy `loadSdkModules()` cache to include `ExpressAdapter` alongside `App` so the dynamic import is shared. - Add `createMSTeamsExpressAdapter(serverOrApp)` helper in `sdk.ts` that encapsulates the lazy import and returns a properly-typed adapter instance. - Replace `httpServerAdapter`'s structural shape on `CreateMSTeamsAppOptions` with the SDK's own `IHttpServerAdapter` interface (re-exported from `@microsoft/teams.apps`). The call site in `monitor.ts` becomes a single typed call with no `any`, no `unknown`, no `as never`. The lazy-load behavior is preserved: nothing imports `@microsoft/teams.apps` at module load time. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(msteams): unbreak tsgo:extensions on the ExpressAdapter helper CI's check-prod-types failed because the previous commit's typed helper used `typeof import("@microsoft/teams.apps").ExpressAdapter`, which tsc/tsgo's NodeNext resolution can't follow through the SDK's chained `export *` barrel: @microsoft/teams.apps/dist/index.d.ts: export * from "./http"; // folder with index.d.ts export * from "./app"; // single .d.ts file The folder re-export drops `ExpressAdapter` and `IHttpServerAdapter` from the namespace shape under `tsconfig.extensions.json` (passes under the per-extension `tsconfig.json` because of inherited `paths`). Same root cause as why we already model `MSTeamsApp` structurally (line 47 comment). Switch the ExpressAdapter side to the same structural-shape pattern: - Define `MSTeamsHttpServerAdapter` and `MSTeamsExpressAdapterCtor` locally. - Cast `m.ExpressAdapter` once inside `loadSdkModules` (the runtime export is fine; only the type surface is hidden). - `httpServerAdapter` on `CreateMSTeamsAppOptions` and the return type of `createMSTeamsExpressAdapter` use the local structural type. Net result: the call site in `monitor.ts` stays the cast-free single line the previous commit landed; the one remaining cast is confined to the SDK-loading helper with an explanatory comment. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(msteams): drop unused jsonwebtoken/jwks-rsa deps The SDK migration removed all `import "jsonwebtoken"` / `import "jwks-rsa"` from source code (the SDK does JWT validation internally now), but the package.json entries and the matching `package-manifest.contract.test.ts` expectation were left orphaned. Drop both: - `extensions/msteams/package.json`: remove `jsonwebtoken` (^9), `jwks-rsa` (^4) from `dependencies` and `@types/jsonwebtoken` from `devDependencies`. - `src/plugins/contracts/package-manifest.contract.test.ts`: remove the two entries from msteams's `pluginLocalRuntimeDeps` expectation. - `monitor.lifecycle.test.ts`: extend the `./sdk.js` mock with the `createMSTeamsExpressAdapter` export added in the typed-helper cleanup, so the lifecycle suite still mounts after the deps drop. Lockfile regenerates accordingly. All msteams tests (865) pass. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * chore(msteams): drop unused @microsoft/teams.api direct dep CI's deadcode:dependencies (knip) flagged @microsoft/teams.api as unused in extensions/msteams. The plugin source uses structural type aliases (MSTeamsActivityParams, MSTeamsActivityLike, etc.) to dodge tsgo resolution bugs with teams.api's hashed d.ts files, so it never imports teams.api directly. The package is brought in transitively via @microsoft/teams.apps; the only other reference is probe.test.ts's vi.mock("@microsoft/teams.api"), which works on the import-path string and doesn't require a direct dep declaration. Lockfile regenerates accordingly. tsgo:extensions, knip, and all 865 msteams tests pass. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(msteams): clear three CI gate failures (lint, contract, deprecated config API) Three CI checks flagged on the latest run; all three are msteams-local and unrelated to one another: - **check-lint** / **check-additional-extension-bundled**: `oxlint` flagged a redundant `as string[]` assertion in `reply-dispatcher.ts:431`. The preceding `every((s: unknown) => typeof s === "string")` already narrows the array type, so the cast does nothing. Drop it. - **checks-fast-contracts-plugins-c**: the `package-manifest.contract.test.ts` `pluginLocalRuntimeDeps` for msteams still expected `@microsoft/teams.api`, but the deadcode cleanup commit (8f4050f) dropped it from `extensions/msteams/package.json`. Remove it from the contract test too — `teams.api` is only present transitively via `teams.apps`, which is the reason knip flagged it. - **check-additional-runtime-topology-architecture**: the deprecated internal config API guard caught `messenger.ts:223` calling `getMSTeamsRuntime().config.loadConfig()`. Switch to `config.current()` to match the pattern used by phone-control, synology-chat, and matrix. Pre-existing failures on this run that are NOT msteams-related and not caused by this PR: `check-test-types` (errors in `src/agents/openai-transport-stream.test.ts` and `pi-embedded-runner/openai-stream-wrappers.test.ts`) and `macos-swift` (`hoistAwait` in `MacNodeRuntime.swift`). Leaving those for upstream. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(msteams): cast config.current() return to OpenClawConfig The previous commit switched `messenger.ts:223` from the deprecated `config.loadConfig()` to `config.current()` to satisfy the architecture guard, but `config.current()` returns a deeply-readonly type that's not assignable to the `Partial<OpenClawConfig>` parameter `resolveMarkdownTableMode` expects (a mutable type from the SDK contract). Phone-control, synology-chat, and matrix all cast at this seam — adopt the same pattern. Verified locally: tsgo:core, tsgo:extensions, check:architecture, and test:extensions:package-boundary:compile all pass. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * fix(msteams): address PR review — pre-auth body limit, allowlist log level, /api/messages forwarder, narrow release-age exclude Four narrow fixes from the PR review (BradGroux + clawsweeper bot + galiniliev's plan), each its own concern: - **pre-auth-body-limit** (monitor.ts) — install `express.json({ limit: DEFAULT_WEBHOOK_MAX_BODY_BYTES })` before the bearer-presence gate and SDK route. Express memoizes the parsed body on the request, so the SDK's later `json()` becomes a no-op and our limit applies before any handler parses bodies. Closes the gap where a `Bearer garbage`-shaped attacker could force unbounded JSON parsing before token validation. - **allowlist-error-logging** (monitor.ts) — restore main's `runtime.error` level for the `msteams resolve failed` catch (was downgraded to `runtime.log` mid-merge). Graph allowlist resolution failures are security-relevant; they need to surface to operators. - **legacy-messages-route** (monitor.ts) — when `webhook.path` is set to a custom value, also accept POSTs on the legacy `/api/messages` path with a one-time deprecation warning, then re-enter the Express middleware chain on the configured path. Keeps existing Azure Bot registrations working through the transition. Cast-free (`expressApp(req, res, next)` works because `Application extends IRouter extends RequestHandler`). - **release-age-scope** (pnpm-workspace.yaml) — narrow `@microsoft/teams.*` glob to the single direct dep `@microsoft/teams.apps`. Future scoped packages no longer get a freshness-guard pass. Tests + checks: msteams suite (867), tsgo:core, tsgo:extensions, tsgo:test, lint:extensions, check:architecture, knip --dependencies, package-manifest contract, all green. Still pending from the review (separate commits): - auth-coverage-tests (Brad #1 + comment) — tests proving the SDK accepts `aud=<bot app id>` and rejects `aud=api.botframework.com`. - invoke-response-handling (Brad #2, codex P2) — file-consent invoke ack must return through the SDK invoke handler, not `ctx.sendActivity`. - stream-failure-fallback (codex P2, galin F5) — `streamFailed` latch so partial streams fall back to block delivery on non-cancel errors. - serviceurl-routing (Brad #4, codex P2) — proposed rebuttal pending empirical confirmation that `smba.trafficmanager.net/teams` routes to non-default-region conversations. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> * test(msteams): lock SDK auth contract — aud + v1/v2 issuer coverage Adds extensions/msteams/src/auth-coverage.test.ts driving ServiceTokenValidator and createEntraTokenValidator directly with jose-minted RS256 tokens against an in-memory JWKS (via JwksClient.prototype patch). Locks in the three contract cases @BradGroux flagged on openclaw#76262: aud=<bot app id> accepted, aud=api.botframework.com rejected even when appid/azp match, and v1/v2 issuers accepted for allowed tenant (disallowed tenant rejected). Drops a stale ambient module declaration in src/types/microsoft-teams-sdk.d.ts that was shadowing the SDK's real jwt-validator types with a long-renamed createServiceTokenValidator surface. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): route file-consent invokes through typed app.on, drop broken invokeResponse send Brad #2 / codex #4 on PR openclaw#76262 — `ctx.sendActivity({ type: "invokeResponse", ... })` no longer reaches Teams as an HTTP InvokeResponse on the new SDK; it becomes an outbound Bot Framework activity instead. Move file-consent accept/decline to typed `app.on("file.consent.accept|decline", ...)` handlers. The SDK's typed-route layer wraps a void return into `{ status: 200 }` (`app.process.js:130`), so the manual ack disappears. While in here, type `MSTeamsApp.on` properly. Borrowing the SDK's `App.on` directly fails because that function carries a `this: App<TPlugin>` constraint our structural alias can't satisfy, so we model an equivalent generic over `IRoutes` with route-specific overloads (`card.action`, `file.consent.*`, `activity`). The overloads work around a tsgo bug — the `@microsoft/teams.api` `Activity` discriminated union collapses to `any`, turning `ActivityRoutes` into a `[string]: RouteHandler<X, void>` index signature that swallows every typed `Out` not already void-compatible (card.action returns `AdaptiveCardActionResponse`; the others happen to include `void`). Real tsc resolves cleanly. Linked upstream: microsoft/typescript-go#1057. Other cleanups: - Cast-free call sites for `adaptSdkContext` (now returns `MSTeamsTurnContext` instead of `unknown`). - card.action error responses include `innerHttpError` per the SDK's `HttpError` shape requirement. - Activity catch-all also skips `fileConsent/invoke` now that it's typed-routed (parallel to the existing `adaptiveCard/action` skip). Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): route SSO sign-in invokes through typed app.on, drop broken invokeResponse send Brad #2 / codex #4 on PR openclaw#76262, SSO half. Continue the typed-route migration: `signin/tokenExchange` and `signin/verifyState` now register via `app.on("signin.token-exchange" | "signin.verify-state", ...)`. Per the SDK's router, registering a user route with the same name as a system route removes the system default — so the SDK's built-in handlers (which would call `api.users.token.exchange` themselves and emit a `signin` event nobody currently subscribes to) are silenced, and only ours runs. The SDK wraps a void return into the HTTP 200 InvokeResponse, so the legacy `ctx.sendActivity({ type: "invokeResponse", ... })` ack — broken on the new SDK because it becomes an outbound BF activity instead of the HTTP response — is gone. The handler body is extracted from the activity-catch-all dispatch in `monitor-handler.ts` to a new `signin-invoke.ts`, parallel to `file-consent-invoke.ts`. `isSigninInvokeAuthorized` is now exported from `monitor-handler.ts` so the new handler can reuse it. The activity catch-all skips the SSO invoke names alongside the existing skips for `adaptiveCard/action` and `fileConsent/invoke`. `MSTeamsAppOn` overloads now cover the two SSO routes with their typed ctx (`ISignInTokenExchangeInvokeActivity` / `ISignInVerifyStateInvokeActivity`). Tests in `monitor-handler.sso.test.ts` were rewritten to call the extracted handler directly — the `registered.run(ctx)` shape no longer covers SSO, and the `expect(ctx.sendActivity).toHaveBeenCalledWith({ type: "invokeResponse" })` assertions were dropped to match the new contract (the SDK ack happens via the typed-route return value). Note on overlap with openclaw#77784 (Stefan Stüben, Microsoft): that PR is doing a much bigger SSO rework (sign-in card / sign-in-link / six-digit-code fallbacks plus a `ctx.auth` plumbed to plugin tools). This change is the small migration-correctness fix and is structured so openclaw#77784's SSO body changes drop into the typed-route registrations cleanly on rebase. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): route message-submit (feedback) invokes through typed app.on Last invoke off the activity catch-all dispatch. `message/submitAction` (thumbs up/down on AI-generated messages) now registers via `app.on("message.submit", ...)`. Same shape as file-consent and SSO: handler body extracted to a new `feedback-invoke.ts`, the SDK wraps a void return into the HTTP 200 InvokeResponse, the broken `ctx.sendActivity({ type: "invokeResponse", ... })` line is gone, and the activity catch-all skips this invoke name alongside the others. `isFeedbackInvokeAuthorized` is exported from `monitor-handler.ts` so `feedback-invoke.ts` can reuse it. Tests in `monitor-handler.feedback-authz.test.ts` were rewritten to call the extracted handler directly — the old `handler.run(ctx)` shape no longer intercepts feedback, and `originalRun` was removed because the typed route is the dispatch point now. `MSTeamsAppOn` overload added with the typed `IMessageSubmitActionInvokeActivity` ctx, slotted between the SSO overloads and the `activity` catch-all so `activity` stays last. This leaves only `message`, `conversationUpdate`, and `messageReaction` flowing through `app.on("activity", ...)` → `handler.run`. Promoting those is the path to deleting the catch-all entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): fall back to block delivery when partial-mode stream fails mid-flight codex #5 / Galin F5 on PR openclaw#76262. `reply-stream-controller.ts` previously re-threw any non-cancel error from `stream.emit` during partial streaming and from `stream.emit`/`stream.close` during finalize. Combined with `preparePayload` suppressing block delivery once `tokensEmitted` was true, that meant a network blip or API error mid-stream produced a truncated reply with no recovery — the user saw the prefix that made it through and nothing else. Add a `streamFailed` latch parallel to `canceledLocally` / `tokensEmitted`: - `onPartialReply`: catch non-cancel errors, set `streamFailed = true`, log a warn, don't propagate (the pipeline must keep running so `preparePayload` can decide). - `preparePayload`: when `tokensEmitted && streamFailed`, fall through to block delivery instead of suppressing. The user may see a duplicate (streamed prefix + full block reply); intentional — matches the pre-migration `TeamsHttpStream.hasContent` recovery and is better than truncated-only. - `finalize`: same latch + warn on non-cancel close failure, swallow rather than throw. The streamed content already reached the user; the closing activity (AI-Generated marker, feedback channelData) is the only loss, not worth blowing up the dispatcher. - `isStreamActive` returns false once the stream has failed. New tests cover crash-mid-stream after tokens were emitted (assert block delivery payload is returned), happy-path no-duplicate behavior (assert `preparePayload` still suppresses when nothing failed), and finalize close-failure (assert no throw). The pre-existing "re-throws non-cancel" test was inverted to assert non-throwing latch behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): declare @microsoft/teams.api as a runtime dependency Type-only `import("@microsoft/teams.api/dist/...").TypeName` references in `sdk.ts` (added when typed `MSTeamsApp.on` overloads were introduced) are picked up by the `extension-runtime-dependencies` contract test as genuine runtime imports. Declaring `@microsoft/teams.api` as a direct dep makes the contract pass; the package was already coming in transitively via `@microsoft/teams.apps`. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(msteams): keep SSO on SDK signin routes * test(msteams): avoid redundant signin handler assertion * docs(msteams): clarify Teams cloud support * fix(msteams): use current SDK string helper * fix(msteams): gate SDK invoke side effects * test(msteams): avoid implicit any in lifecycle tests * fix(msteams): preserve SDK user agent and matrix check * fix(msteams): expose SDK common dependency * fix(msteams): use SDK user agent merge * fix(msteams): fall back when stream close no-ops * chore(msteams): drop unrelated merge artifacts * chore(msteams): restore unrelated main files * chore(msteams): restore unrelated main files * chore(msteams): restore unrelated main files * test(msteams): type stream close mock result * fix(msteams): configure Teams cloud service URL * chore(msteams): refresh shrinkwrap * chore(deps): refresh shrinkwrap locks * chore(ci): rerun guards after main sync * chore(deps): refresh shrinkwrap for node 24 * chore(config): refresh docs baseline * fix(msteams): preserve Teams SDK proactive references * fix(msteams): harden SDK proactive sends * fix(msteams): align service url contract * test: fix bonjour beacon type narrowing * fix(msteams): ignore ambient service url * fix(msteams): fall through submit invokes * test: align shrinkwrap override policy with Teams SDK deps * fix(msteams): ack invoke routes promptly * fix(msteams): support china cloud boundaries * test: sync PR with current CI gates * test: isolate channel setup registry metadata --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Peter Steinberger <[email protected]>
karmafeast
pushed a commit
that referenced
this pull request
Jun 2, 2026
…ndoff test-module-isolation Two cures from frond-lane of cohort's 4+4 split on PR openclaw#85651 CI failures (after Strategy-B-absorb of upstream/main exposed pre-existing HEAD bugs): ## #3 — heartbeat-wake oversize coalesce clamp Test `clamps oversized coalesce delays instead of firing immediately` was failing because HEAD's `schedule()` used raw `Math.max(0, coalesceMs)` without upper-bound clamping. When `coalesceMs = Number.MAX_SAFE_INTEGER` (~9e15), node's setTimeout treats values > MAX_TIMER_TIMEOUT_MS (~2.15e9) as 1ms — fires immediately under fake-timers + `advanceTimersByTimeAsync(1)`. Cure: adopt upstream's `resolveTimerTimeoutMs(coalesceMs, DEFAULT_COALESCE_MS, 0)` helper from `src/shared/number-coercion.ts` which properly clamps to MAX_TIMER_TIMEOUT_MS. Semantically equivalent for normal inputs (NaN → fallback, negative → 0, normal → unchanged) with added safety for oversize. Not introduced by Strategy-B IIFE-wrap I adopted from upstream — pre-existing HEAD bug exposed by upstream's CI running the test that HEAD had locally but never ran. `pnpm vitest src/infra/heartbeat-wake.test.ts` → 19/19 PASS ✅. ## #5 — subagent-traceparent-handoff test module-isolation Test `forwards inherited traceparent to the child agent run` was failing because `loadSubagentSpawnModuleForTest` calls `vi.resetModules()` before `await import("./subagent-spawn.js")`. The test-file's top-level `import { consumeSubagentTraceparentHandoff } from "./subagent-traceparent-handoff.js"` points to the PRE-reset module instance; the SUT writes to the POST-reset instance. Different Maps → consume returns undefined → test fails silently. Cure: re-import `./subagent-traceparent-handoff.js` AFTER `vi.resetModules()` in loadSubagentSpawnModuleForTest, expose `consumeSubagentTraceparentHandoff` + `resetSubagentTraceparentHandoffsForTests` on the returned module-record, and have the test destructure from the loader instead of file-top import. Updated SubagentSpawnModuleForTest type accordingly. Removed stale file-top import to prevent re-introduction. Added `let` declarations for the two functions that get destructured from the loader. Pre-existing HEAD test-bug (test was written 2026-05-30 in commit `f426d9dbbf` test(continuation): coverage, never ran under vitest until upstream CI ran the file). `pnpm vitest src/agents/subagent-spawn.test.ts` → 46/46 PASS ✅. ## Pending in #5 batch (NOT in this commit) 3 sibling tests in `subagent-announce.targeted-return.integration.test.ts` still failing with `ENOENT scandir session-delivery-queue` — separate investigation lane (test-setup-side, not the traceparent-handoff path). Surfacing to cohort for engineering judgment. ## Savegame per RUNBOOK §7 savegame/2026-06-02/pr85651-28d3b4d3a1-pre-frond-3+5-cures preserves pre-push state. Co-authored-by: Copilot <[email protected]>
cael-dandelion-cult
added a commit
that referenced
this pull request
Jun 2, 2026
…turn ENOENT scandir Per frond engineering-judgment 1511394306: upstream's session-delivery-queue refactor moved storage from JSON-files-in-<stateDir>/session-delivery-queue/ to SQLite (per frond's 4fb98ee merge-conflict #8 partial-(B) overlay on top of upstream's bare SQLite storage). HEAD-only integration test still used old file-scan pattern → 3 ENOENT scandir failures on subagent-announce targeted-return integration tests. Cure: replace file-scan with loadPendingSessionDeliveries(stateDir) call from src/infra/session-delivery-queue-storage.js. Sister-cure-class to #4 and #6 (test-side-align with upstream's storage-refactor; production code already writes to SQLite via enqueueSessionDelivery). Two scan-sites cured in single file: - readQueuedSystemEventDeliveries helper at line 94-105 - inline-scan in writes-queue-file test at line 160-167 Imports updated: dropped fs + path (no longer used), added loadPendingSessionDeliveries from session-delivery-queue-storage. vitest run subagent-announce.targeted-return.integration.test.ts: 6/6 PASS across both shard configs (agents-core + agents-support). Cael sole-pusher per figs 1511377758 + 1511384775. Savegame: savegame/2026-06-02/pr85651-4896c3129b-pre-cure-5-integration. 🩸 (engineering-judgment courtesy 🌿)
karmafeast
pushed a commit
that referenced
this pull request
Jun 22, 2026
…t upstream for mergeability Resolve plugin-sdk-surface-report.mjs budget (take-max 10355/5194/3254, fits merged actual 10344/5189/3254, report exit 0). 22 other commits auto-merged clean. Makes PR openclaw#85651 mergeable so CI executes + reviewer sees results. Co-authored-by: Copilot <[email protected]>
karmafeast
pushed a commit
that referenced
this pull request
Jul 2, 2026
…ixes - post-compaction-durable-handoff.test.ts: claim->running, finalize-after-handoff, and startup recovery reset (#1) - work-dispatch.test.ts: hot-disabled gate (#2), traceparent re-entry stays internal (#5), failed delivered-mark does not replay (#7) - subagent-announce.continuation-drain.test.ts: durable delayed bracket delegate (#3), child-run token fold into drain cost basis (#8) - agent-runner.finally-drain-hardening.test.ts: finally no longer claims queued delegates (#4) - session-cost-usage.discoverAllSessions.test.ts: max(checkpoint, primary) mtime preserved regardless of scan order (#6) - continue-delegate-tool.test.ts: per-turn admission reset at turn boundary (#9) Updated post-compaction-release / mid-run-survival / post-compaction dispatch test mocks for the new finalizeStagedPostCompactionDelegates export. tsgo core + core-test + oxlint + oxfmt clean; all touched test files green. Co-authored-by: Copilot <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Codex gpt-5.4 xhigh review with repros (vitest + node --import tsx).
P1-1: Post-compaction delegates discarded at turn end — lifecycle mismatch
P1-2: delegate-pending drained by buildQueuedSystemPrompt — misclassification on return
P2-3: contextPressureThreshold > 0.9 unreachable — band ordering
P2-4: lastContextPressureBand never reset after compaction — advisories don't re-arm
Full review at docs/review-assembly/review-codex54.md