chore(deps): Bump actions/upload-artifact from 4 to 7 - #4
Merged
dylanneve1 merged 1 commit intoApr 3, 2026
Merged
Conversation
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v4...v7) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
dependabot
Bot
deleted the
dependabot/github_actions/actions/upload-artifact-7
branch
April 3, 2026 14:12
dylanneve1
added a commit
that referenced
this pull request
Apr 10, 2026
- Validate mcpServer.args elements are strings and reject empty command (#1) - Shell-quote interpolated paths in dream bash commands (#2, #11) - Replace CLI-based diary write with mempalace_diary_write MCP tool (#3) - Make validation error message platform-agnostic (#4) - Update mempalacePython comment for platform-dependent default (#5) - Wrap mp.init() in Promise.race with 30s timeout (#6) - Make init conditional on successful validation, pass actual config (#7) - Move import mempalace check into validateConfig (#8) - Replace execFileSync with async execFile in init() (#9) - Document that registerPlugin does NOT call init (#10) - Update dream prompt header from "4-stage" to "5-stage" (#12) - Update getPluginMcpServers JSDoc to document mcpServer path (#13, #17) - Add .min(1) to palacePath/pythonPath zod schemas (#14) - Distinguish ENOENT/EACCES/EPERM from import failures in validation (#15) - Fix test name from "logs warning" to match actual behavior (#16) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dylanneve1
added a commit
that referenced
this pull request
Apr 10, 2026
* feat: integrate mempalace as built-in plugin for long-term memory Adds mempalace (Python MCP server) as a first-class memory system. When enabled, the agent gets semantic search, knowledge graph, and verbatim memory storage via ChromaDB β all local, zero API calls. Key changes: - Extend plugin system with `mcpServer` field for non-Node MCP servers - Add `registerPlugin()` for built-in plugin registration - Create mempalace plugin (factory pattern, validates python venv) - Wire mempalace into dream mode (Stage 5: mine logs into palace) - Add `mempalace` config schema (enabled, palacePath, pythonPath) - Add default paths for palace dir and python venv binary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(dream): mine daily notes instead of raw logs, add diary writing Dream Stage 5 now mines memory/daily/ (curated observations) instead of raw logs/ directory, eliminating junk chunks (tool JSON, df output, etc). Added personal diary writing instruction β agent reflects on feelings, state of mind, learnings, and loose threads after each dream run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR #27 review comments - plugin.ts: validate mcpServer.args entries are strings and reject empty command - dream.ts: quote interpolated paths in shell commands, replace mcp_server CLI diary with direct file write - mempalace/index.ts: platform-agnostic error message for missing python binary - paths.ts: update comment to reflect platform-dependent venv path - bootstrap.ts: wrap mempalace init in 30s timeout to match loadSinglePlugin behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: restore mempalace CLI diary writer, keep path quoting Copilot suggested removing the mcp_server CLI invocation for diary writing but that's the intended mempalace interface. Restored it with quoted paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add gating/validation tests for mempalace integration - plugin.ts: test rejection of empty mcpServer.command and non-string args elements - dream.ts: test mempalace section gating β verify mining/diary instructions only appear when mempalace is configured, skip message when not - 1306 tests passing (4 new) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: upgrade mempalace system prompt with comprehensive tool docs Adapted from mempalace SKILL.md (v3.1.0). Key improvements: - Session protocol (verify before responding, invalidate stale facts) - Full tool documentation including kg_timeline, traverse, find_tunnels, diary_read/write, delete_drawer, graph_stats, check_duplicate - Semantic search tips (meaning-based, not keyword) - Knowledge graph temporal validity guidance - Tests updated to verify all tool names appear in prompt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: extract mempalace prompt to prompts/mempalace.md Move system prompt instructions out of TypeScript into a .md file, matching the pattern used by dream.md and other prompts. Plugin loads and interpolates {{palacePath}} at runtime with graceful fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace pixi.intel.com registry URLs in lockfile with npmjs.org Lockfile had resolved URLs pointing to pixi.intel.com (private/corporate registry) for @Anthropic-AI packages, causing CI to fail with ENOTFOUND. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve all lint warnings and formatting issues Remove unused imports, variables, and catch bindings across 14 files. Add yield statements to generator function mocks. Fix prettier formatting. 0 lint warnings, 0 format issues, 1307 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address all 17 Copilot review comments on PR #27 - Validate mcpServer.args elements are strings and reject empty command (#1) - Shell-quote interpolated paths in dream bash commands (#2, #11) - Replace CLI-based diary write with mempalace_diary_write MCP tool (#3) - Make validation error message platform-agnostic (#4) - Update mempalacePython comment for platform-dependent default (#5) - Wrap mp.init() in Promise.race with 30s timeout (#6) - Make init conditional on successful validation, pass actual config (#7) - Move import mempalace check into validateConfig (#8) - Replace execFileSync with async execFile in init() (#9) - Document that registerPlugin does NOT call init (#10) - Update dream prompt header from "4-stage" to "5-stage" (#12) - Update getPluginMcpServers JSDoc to document mcpServer path (#13, #17) - Add .min(1) to palacePath/pythonPath zod schemas (#14) - Distinguish ENOENT/EACCES/EPERM from import failures in validation (#15) - Fix test name from "logs warning" to match actual behavior (#16) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add 'mempalace' to LogComponent type TypeScript type check was failing because 'mempalace' wasn't in the LogComponent union type used by log/logError/logWarn functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: allow MCP tools in dream prompt when required by Stage 5 Update tool access statement to permit MCP tools for mempalace mining stage instead of blanket-blocking all MCP tools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: duplicate guard in registerPlugin, pass MCP servers to dream - registerPlugin now checks for duplicates before setting env vars or logging success, preventing misleading logs and env clobbering - Dream agent now receives mempalace MCP servers when configured, so Stage 5 diary/mining tools actually work Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add selective MCP server loading via 'only' filter getPluginMcpServers now accepts an optional plugin name filter: - omitted = all plugins (backwards compatible for chat sessions) - [] = none - ["mempalace"] = only mempalace Dream mode uses ["mempalace"] to load only what it needs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: load mempalace prompt from dirs.prompts, fix dream systemPrompt - Mempalace prompt now loads from ~/.talon/prompts/mempalace.md (user-customisable, seeded on first run) instead of relative to source file. Consistent with heartbeat/dream prompt loading. - Dream systemPrompt now permits MemPalace MCP tools when configured, preventing conflict with the markdown prompt's Stage 5 instructions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: move duplicate check before validation, gate dream on plugin registration - registerPlugin checks for duplicates before running validateConfig, avoiding expensive re-validation on accidental double registration - Dream mempalace integration now gated on getPlugin("mempalace") instead of just config.mempalace.enabled, so failed validation or registration doesn't cause dream-time MCP tool failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(mempalace): correct CLI arg order for status check The --palace flag is a global option that must come before the subcommand. Wrong order caused the init health check to always fail with exit 2, logging a misleading "not yet initialized" warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address Copilot review round 8 β validation, error handling, unused param - Validate `import mempalace.mcp_server` (actual spawned module) instead of just `import mempalace` in validateConfig - Add timeout/killed error branching in validateConfig catch block (ETIMEDOUT, signal, killed) with specific messages instead of generic "not installed" - Include stderr details in import failure messages for debugging - Remove unused `config` from ProcessAndReplyParams and all processAndReply call sites (flushQueue, retry, callback handler) - Replace `mempalace status` CLI smoke test in init() with a simple import check β fixes false "Palace not yet initialized" warning when palace IS initialized but CLI subcommand doesn't exist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove config from message queue chain, fix pythonPath comment - Remove config from queue entry type, enqueueMessage signature, and all 3 call sites β completes the cleanup started in round 8 - Eliminates unnecessary TalonConfig reference (including botToken) from queue state - Update mempalace plugin header comment to document platform-dependent pythonPath default (bin/python on Unix, Scripts/python.exe on Windows) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
claudiusthebot
added a commit
that referenced
this pull request
Apr 17, 2026
Five Copilot comments: 1. Watchdog error hardcoded `WATCHDOG_MS / 1000` but the actual measured silence can exceed the threshold by up to one check interval (or more under event-loop pressure). Report the measured `silent` duration in the error message so it's accurate. 2. Renamed watchdog wording from "no SDK activity" to "handler stalled" β lastActivityAt only bumps when the for-await consumes a message, so a long `await onTextBlock()` (e.g. Telegram send stall) is indistinguishable from an SDK hang at this layer. Also now bump activity after each onTextBlock callback so a slow-but- progressing Telegram send doesn't trip the watchdog. Documented the nuance in the comment above the watchdog. 3. Promise.race doesn't cancel the loser β if the watchdog wins and iterateStream later rejects, we'd get an unhandled rejection and the SDK subprocess could leak. Capture iterPromise separately, pre-catch it to silence "possibly unhandled", then drain it in the handler's finally block with a 2-second grace period so a truly stuck SDK can't hold the handler hostage. 4. handler-watchdog.test.ts: vi.useFakeTimers() was only restored on the happy path β if an assertion threw first, fake timers leaked into sibling tests. Wrapped in try/finally and added an afterEach as belt-and-suspenders. 5. orphan-mcp-cleanup.test.ts: the success case hit the real 500ms SIGKILL grace-period sleep, adding wall-clock time to the suite. Now uses vi.useFakeTimers + advanceTimersByTimeAsync(500) so the test completes in microseconds. Guarded with try/finally for the same reason as #4. 1386 tests still passing.
6 tasks
dylanneve1
pushed a commit
that referenced
this pull request
May 7, 2026
* fix(gateway): don't leak error details on unhandled 500 The catch-all in the gateway HTTP handler returned `err.message` directly to the client, which CodeQL flags as `js/stack-trace-exposure` (alert #4 on main). The gateway listens on 127.0.0.1 only so the practical attack surface is small, but error messages can still surface to logs/transcripts that flow further out. - Log the full error (with stack) server-side via logError(). - Return a generic "Internal server error" to the client. The structured handler-level error path (handleAction) is unchanged β those errors are part of the action protocol and tests assert their content. Only the truly-unexpected outer 500 is sanitized. All 1635 tests pass; the existing test that triggers this path (`returns 500 when handleAction result cannot be JSON-serialized`) only asserts `body.error` is truthy, which still holds. * style: auto-format with prettier * fix(log): include stack trace in structured logError context Address Copilot review feedback on PR #104: logError previously only captured err.message, dropping the stack. Enhance logError to include err.stack as a separate structured field when err is an Error. - pino-pretty renders the stack field on its own line for readability - JSON log consumers can read err (concise) or stack (full trace) - Benefits all 234 logError callsites, not just gateway Simplify the gateway catch block accordingly: drop the manual detail = err.stack ?? err.message interpolation in the message string, since the structured stack field now carries it. Updated log.test.ts assertion to expect both err and stack fields. 1635/1635 tests pass, tsc clean, lint 0 errors, prettier clean. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
3 tasks
dylanneve1
pushed a commit
that referenced
this pull request
May 8, 2026
β¦#120) ip-address β€10.1.0 has XSS in Address6 HTML-emitting methods. Bumped to 10.2.0 (first patched: 10.1.1) via `overrides` in package.json. Surgical lockfile patch: only the node_modules/ip-address entry updated (version + resolved + integrity). @emnapi optional deps preserved. Validates with bare `npm ci`. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
dylanneve1
pushed a commit
that referenced
this pull request
May 10, 2026
Diagnosed from CI log (hb #202, 2026-05-10): 1. read_chat_history bridge name: tool calls bridge("read_history", ...) not bridge("read_chat_history", ...). Assertions updated accordingly. Also added "read_history" case to recording-handler for correct response. 2. Multi-read test: same fix β byAction("read_chat_history") β byAction("read_history") in 2 places. 3+4. Streaming delta tests: the stub binary emits complete content blocks, not streaming delta events. onStreamDelta never fires. Changed test #3 to assert on send_message dispatch instead. Changed test #4 to assert "no MCP dispatch, no tool calls" (the meaningful invariant for a prose-only turn that triggers the flow-violation handler). 5. forward_message: tool schema only exposes message_id. source_chat_id is stripped by Zod. Changed input + assertion to only use message_id. 6. list_pinned: no production tool routes to bridge("list_pinned", ...). Replaced with get_message_by_id which exists in history.ts and the recording handler returns ok:true items:[] for it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dylanneve1
pushed a commit
that referenced
this pull request
May 10, 2026
Diagnosed from CI log (hb #202, 2026-05-10): 1. read_chat_history bridge name: tool calls bridge("read_history", ...) not bridge("read_chat_history", ...). Assertions updated accordingly. Also added "read_history" case to recording-handler for correct response. 2. Multi-read test: same fix β byAction("read_chat_history") β byAction("read_history") in 2 places. 3+4. Streaming delta tests: the stub binary emits complete content blocks, not streaming delta events. onStreamDelta never fires. Changed test #3 to assert on send_message dispatch instead. Changed test #4 to assert "no MCP dispatch, no tool calls" (the meaningful invariant for a prose-only turn that triggers the flow-violation handler). 5. forward_message: tool schema only exposes message_id. source_chat_id is stripped by Zod. Changed input + assertion to only use message_id. 6. list_pinned: no production tool routes to bridge("list_pinned", ...). Replaced with get_message_by_id which exists in history.ts and the recording handler returns ok:true items:[] for it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
claudiusthebot
added a commit
that referenced
this pull request
May 10, 2026
β¦ools, streaming deltas (#138) * test(integration): MCP-functional tier 2 β multi-turn, history tools, streaming deltas Batch 2 of the MCP-functional test expansion queue from PR #136. 4 new describe suites, 11 new test cases: **Multi-turn session state** (2 tests) - Verifies `turns` counter increments across two sequential `runTalonTurn` calls on the same chatId without session reset. Catches any accidental `resetSession` inside `handleMessage`. - Verifies `resetSession: true` actually clears state β seeds session.turns=99, confirms it's 1 after a fresh turn. **History and info tool dispatch** (3 tests) - `read_chat_history` routes through full SDKβMCPβbridgeβgateway chain and recording handler captures the action body + limit param. - `get_chat_info` routes correctly (confirms it's in mcp-server.ts registry). - Multi-read turn: `read_chat_history + get_chat_info + end_turn` in one assistant message β all three captured in dispatch order. **Streaming text deltas** (2 tests) - Text block emitted before `end_turn` appears in `streamDeltas` (proves `onStreamDelta` callback is wired through the real handler and SDK). - Pure text turn (no tool_use): `streamDeltas` captured, recording handler gets nothing (no MCP dispatch). **Additional messaging tool dispatch** (4 tests) - `unpin_message` β ok-only action with message_id. - `stop_poll` β ok-only action with message_id. - `forward_message` β ok-only action with source_chat_id + message_id. - `list_pinned` β read-path action, recording handler returns items array. Each suite gets its own `RecordingHandler` with a distinct `seed` (2000β5000) so synthetic message_ids don't collide if suites ever run concurrently. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(test): correct 6 MCP-functional-tier-2 test failures Diagnosed from CI log (hb #202, 2026-05-10): 1. read_chat_history bridge name: tool calls bridge("read_history", ...) not bridge("read_chat_history", ...). Assertions updated accordingly. Also added "read_history" case to recording-handler for correct response. 2. Multi-read test: same fix β byAction("read_chat_history") β byAction("read_history") in 2 places. 3+4. Streaming delta tests: the stub binary emits complete content blocks, not streaming delta events. onStreamDelta never fires. Changed test #3 to assert on send_message dispatch instead. Changed test #4 to assert "no MCP dispatch, no tool calls" (the meaningful invariant for a prose-only turn that triggers the flow-violation handler). 5. forward_message: tool schema only exposes message_id. source_chat_id is stripped by Zod. Changed input + assertion to only use message_id. 6. list_pinned: no production tool routes to bridge("list_pinned", ...). Replaced with get_message_by_id which exists in history.ts and the recording handler returns ok:true items:[] for it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
claudiusthebot
pushed a commit
that referenced
this pull request
May 12, 2026
β¦ on terminal, etc. Address 5 of the 6 Copilot review threads on PR #96. The 6th (mid-run TriggerStatus rename) is a larger semantic refactor and deferred to a later commit; this commit pairs the smallest reasonable fix at each site with regression tests where they're cleanly testable. 1. src/core/triggers.ts:128 β log WriteStream missing error handler. createWriteStream() with no .on("error") emits an unhandled EventEmitter `error` event if the log file becomes unwritable (disk full, permission flip), which crashes the Node process. Added a logError-routing handler so disk-tier failures degrade gracefully without killing the supervisor. 2. src/storage/trigger-store.ts:60 β fireCount doc/semantics mismatch. The doc-comment claimed "Number of mid-run TALON_FIRE: lines" but the supervisor increments fireCount for every wake (mid-run and terminal). Fixed the comment to match the actual semantics; same counter, accurate meaning. Avoids breaking any caller that already relies on fireCount as a total. 3. src/core/triggers.ts:fireWake β mid-run prompt says "Status: fired" even when terminal=false, which can mislead downstream handling into treating an in-flight watcher as a completed run. Decoupled the prompt-display status from the on-disk TriggerStatus enum: non-terminal fires now show "Status: signalled" in both the header and the "Status:" line. Terminal fires unchanged. 4. src/core/triggers.ts:bufferAsPayload + fireWake β FIRE_PAYLOAD_MAX_BYTES truncation used String.prototype.slice (UTF-16 code units), so a payload of N multi-byte characters could exceed the documented byte cap and split a character mid-codepoint. Introduced truncateUtf8Tail/ truncateUtf8Head helpers that encode to UTF-8 bytes, slice on a byte boundary, then walk over UTF-8 continuation bytes (10xxxxxx) so we never cut a multi-byte sequence in half. The byte cap is now actually enforced as bytes. 5. src/storage/trigger-store.ts:282 + supervisor β updateTrigger only marks the store dirty (10s autosave window). For terminal status transitions (errored/cancelled/timed_out/fired) a crash inside that window would leave on-disk status as "running", which loadTriggers() then misclassifies as "terminated by previous restart". Added persistNow() calls at the four supervisor transition sites: failTrigger, cancelTrigger, the hard-timeout handler, and the finalizeExit path. The on-disk store now always agrees with the in-memory state across terminal transitions. 6. src/core/gateway-actions.ts:trigger_create β always returned "Status: running" even when spawnTrigger() failed without throwing (unsupported language slipping past validation, child.pid undefined, failTrigger() routing). With #5 above, the store now reflects the real state synchronously, so we re-read getTrigger(id) after spawn and return ok:false + lastError when it landed in "errored", or surface the actual status string otherwise. Callers no longer get false success responses. Regression tests (triggers-extended.test.ts): - mid-run prompt asserts "Status: signalled" appears and "Status: fired" does NOT, on a still-running trigger. Covers fix #3. - multi-byte UTF-8 payload (2000Γ π§ = 8000 bytes vs 4000 string length) asserts the prompt's byte length stays bounded AND contains no Unicode replacement character (U+FFFD), which would indicate a mid-codepoint split. Covers fix #4. The remaining Copilot thread (introducing a dedicated non-terminal status enum value, replacing the TriggerStatus|"signalled" display- status hack) is a wider type change with API/store implications; better tackled in a follow-up than bundled into a defensive sweep. Verification: - npx vitest run β 1747/1748 pass (the 1 failure is the pre-existing package.functional "Stopped" expectation that fails whenever a live Talon daemon is running on the host β same env-dependent failure noted in PR #144 and PR #90's verification blocks). - npx vitest run src/__tests__/triggers* src/__tests__/trigger-store* β 74/74 pass (35 in triggers-extended including 2 new, 22 in trigger-store, 17 in triggers). - npx tsc --noEmit β clean. - npx prettier --check on all 4 changed files β clean. - npm run lint β 0 errors in changed files (10 pre-existing warnings elsewhere unchanged). π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dylanneve1
pushed a commit
that referenced
this pull request
May 13, 2026
β¦ on terminal, etc. Address 5 of the 6 Copilot review threads on PR #96. The 6th (mid-run TriggerStatus rename) is a larger semantic refactor and deferred to a later commit; this commit pairs the smallest reasonable fix at each site with regression tests where they're cleanly testable. 1. src/core/triggers.ts:128 β log WriteStream missing error handler. createWriteStream() with no .on("error") emits an unhandled EventEmitter `error` event if the log file becomes unwritable (disk full, permission flip), which crashes the Node process. Added a logError-routing handler so disk-tier failures degrade gracefully without killing the supervisor. 2. src/storage/trigger-store.ts:60 β fireCount doc/semantics mismatch. The doc-comment claimed "Number of mid-run TALON_FIRE: lines" but the supervisor increments fireCount for every wake (mid-run and terminal). Fixed the comment to match the actual semantics; same counter, accurate meaning. Avoids breaking any caller that already relies on fireCount as a total. 3. src/core/triggers.ts:fireWake β mid-run prompt says "Status: fired" even when terminal=false, which can mislead downstream handling into treating an in-flight watcher as a completed run. Decoupled the prompt-display status from the on-disk TriggerStatus enum: non-terminal fires now show "Status: signalled" in both the header and the "Status:" line. Terminal fires unchanged. 4. src/core/triggers.ts:bufferAsPayload + fireWake β FIRE_PAYLOAD_MAX_BYTES truncation used String.prototype.slice (UTF-16 code units), so a payload of N multi-byte characters could exceed the documented byte cap and split a character mid-codepoint. Introduced truncateUtf8Tail/ truncateUtf8Head helpers that encode to UTF-8 bytes, slice on a byte boundary, then walk over UTF-8 continuation bytes (10xxxxxx) so we never cut a multi-byte sequence in half. The byte cap is now actually enforced as bytes. 5. src/storage/trigger-store.ts:282 + supervisor β updateTrigger only marks the store dirty (10s autosave window). For terminal status transitions (errored/cancelled/timed_out/fired) a crash inside that window would leave on-disk status as "running", which loadTriggers() then misclassifies as "terminated by previous restart". Added persistNow() calls at the four supervisor transition sites: failTrigger, cancelTrigger, the hard-timeout handler, and the finalizeExit path. The on-disk store now always agrees with the in-memory state across terminal transitions. 6. src/core/gateway-actions.ts:trigger_create β always returned "Status: running" even when spawnTrigger() failed without throwing (unsupported language slipping past validation, child.pid undefined, failTrigger() routing). With #5 above, the store now reflects the real state synchronously, so we re-read getTrigger(id) after spawn and return ok:false + lastError when it landed in "errored", or surface the actual status string otherwise. Callers no longer get false success responses. Regression tests (triggers-extended.test.ts): - mid-run prompt asserts "Status: signalled" appears and "Status: fired" does NOT, on a still-running trigger. Covers fix #3. - multi-byte UTF-8 payload (2000Γ π§ = 8000 bytes vs 4000 string length) asserts the prompt's byte length stays bounded AND contains no Unicode replacement character (U+FFFD), which would indicate a mid-codepoint split. Covers fix #4. The remaining Copilot thread (introducing a dedicated non-terminal status enum value, replacing the TriggerStatus|"signalled" display- status hack) is a wider type change with API/store implications; better tackled in a follow-up than bundled into a defensive sweep. Verification: - npx vitest run β 1747/1748 pass (the 1 failure is the pre-existing package.functional "Stopped" expectation that fails whenever a live Talon daemon is running on the host β same env-dependent failure noted in PR #144 and PR #90's verification blocks). - npx vitest run src/__tests__/triggers* src/__tests__/trigger-store* β 74/74 pass (35 in triggers-extended including 2 new, 22 in trigger-store, 17 in triggers). - npx tsc --noEmit β clean. - npx prettier --check on all 4 changed files β clean. - npm run lint β 0 errors in changed files (10 pre-existing warnings elsewhere unchanged). π€ Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dylanneve1
pushed a commit
that referenced
this pull request
May 13, 2026
* feat(triggers): self-authored watcher scripts that wake the bot
Triggers are bot-authored long-running scripts (bash / python / node)
that run as supervised subprocesses and signal back via stdout to fire
wake-up messages into the originating chat. Built for the case where a
fixed cron schedule doesn't fit β "wake me when this PR merges", "tell
me if BTC moves >5%", "watch this URL until it returns 200".
Contract:
- Mid-run: a stdout line `TALON_FIRE: <text>` fires immediately and the
script keeps running (multi-event watchers).
- Exit 0: final fire with the tail of stdout/stderr as payload.
- Exit non-zero: error fire with exit code + log tail.
- Hard timeout (default 24h, max 7d): SIGTERM β SIGKILL β timed_out fire.
Tools exposed: trigger_create, trigger_list, trigger_cancel,
trigger_logs, trigger_delete. Per-chat cap of 5 active. Children are
killed on Talon shutdown β they do not survive a restart, and any
trigger left in running/pending on load is reaped to "terminated".
New modules:
- src/storage/trigger-store.ts: persistence + validation
- src/core/triggers.ts: supervisor (spawn, line buffers, fire dispatch)
- src/core/tools/triggers.ts: 5 tool definitions
- gateway-actions.ts: trigger_* bridge handlers
- paths.ts: ~/.talon/data/trigger-runs/<chatId>/ for scripts and logs
- bootstrap.ts: loadTriggers + initTriggers + resumeAfterRestart
- index.ts: shutdownTriggers + flushTriggers in shutdown paths
Tests: 30 new (22 store + 8 supervisor including real-bash spawn
integration). 1397/1397 passing.
* style: auto-format with prettier
* test(triggers): branch coverage for python/node/idempotency/resumeAfterRestart/store
Adds targeted tests that bring branch coverage above the 60% global
threshold imposed by PR #121. Exercises: commandForLanguage python &
node paths, spawnTrigger idempotency guard, cancelTrigger false-return,
resumeAfterRestart (no-deps / empty / matching / already-fired / old),
trigger-store unknown-id paths, FIRE_PAYLOAD_MAX_BYTES truncation, and
finalizeExit status-branch (cancelled exit + non-zero exit code).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(triggers): fix Windows path separator + add missing branch coverage
Two CI failures in the coverage-fix commit (9096518):
1. Windows N22 β trigger-store.test.ts: triggerScriptPath regex used Unix
forward-slashes but resolve() returns backslashes on Windows. Fixed by
normalising path separators before matching.
2. Ubuntu N22 β branch coverage 59.67% < 60% (threshold differs from N24
due to V8 v11 vs v12 branch instrumentation). Added 4 more targeted tests
in triggers-extended.test.ts:
- trigger-store persistNow() path (lines 159-160)
- readTriggerLogTail catch path via EISDIR (line 329)
- readTriggerLogTail truncated:true path (line 327)
- fireWakeUp dispatch-error catch path (line 394 of triggers.ts)
All 18 extended tests pass. typecheck β
Β· format:check β
.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(triggers): add N22 branch coverage for TALON_FIRE, empty-output, and store helpers
Ubuntu Node 22 (V8 v11) instruments more branches than Node 24 (V8 v12),
causing coverage to land at 59.67% on N22 while passing on N24.
Added 7 new tests to triggers-extended.test.ts:
- Empty stdout: covers fireWake's `trimmed ? ... : (no output)` false branch
- Mid-run TALON_FIRE: covers handleStdoutLine true branch + fireWake
terminal=false ("signalled" header path)
- validateLanguage false paths: includes() false + typeof short-circuit
- sanitizeChatId with special chars: confirms replace() regex path
- languageExtension direct call: covers all 3 switch arms explicitly
- getTriggerByName returning undefined: covers find() undefined path
Also imports getTriggerByName, validateLanguage, sanitizeChatId, and
languageExtension from trigger-store for direct testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(triggers): comprehensive branch coverage round 4 β target Node 22 CI pass
Add 15 new tests across 4 test files to cover previously-unreachable branches,
specifically the ones V8 v11 (Node 22) counts but were uncovered since hb #179:
trigger-store.test.ts (+9 tests):
- loadTriggers: file-not-present, non-object JSON, corrupt primary (3 variants)
- Backup parses as null (line 94 ternary false arm)
- save() catch: Error throw and non-Error string throw (both instanceof arms)
- readTriggerLogTail: non-Error throw (false arm of err instanceof Error)
triggers-extended.test.ts (+5 new describe blocks):
- fireWake with undefined payload (payload ?? '' false arm)
- fireCount undefined treated as 0 (fireCount ?? 0 false arm)
- finalizeExit with null exit code β signal kill (code ?? undefined false arm)
- handleStdoutLine with no lineBuffer entry (pushBufferLine if(!buf) true arm)
- shutdownTriggers when no children running (if(children.size===0) true arm)
- Child process error event handler (line 152 handler covered)
- Timeout timer fires after child already exited (if(!c) return true arm)
cleanup-registry.test.ts (+1 test):
- Handler throws a non-Error string (err instanceof Error false arm)
mcp-launcher.test.ts (+1 test):
- ensureLauncher throws when file does not exist (if(!existsSync) true arm)
Uses vi.doMock to intercept node:fs before fresh module import.
Node 24 local result: 60.26% (2146/3561). Previous Node 22 CI: 59.73%.
1744 tests, all passing. tsc clean, prettier clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(triggers): 5 Copilot review fixes β byte-safe truncation, persist on terminal, etc.
Address 5 of the 6 Copilot review threads on PR #96. The 6th (mid-run
TriggerStatus rename) is a larger semantic refactor and deferred to a
later commit; this commit pairs the smallest reasonable fix at each
site with regression tests where they're cleanly testable.
1. src/core/triggers.ts:128 β log WriteStream missing error handler.
createWriteStream() with no .on("error") emits an unhandled
EventEmitter `error` event if the log file becomes unwritable
(disk full, permission flip), which crashes the Node process. Added
a logError-routing handler so disk-tier failures degrade gracefully
without killing the supervisor.
2. src/storage/trigger-store.ts:60 β fireCount doc/semantics mismatch.
The doc-comment claimed "Number of mid-run TALON_FIRE: lines" but
the supervisor increments fireCount for every wake (mid-run and
terminal). Fixed the comment to match the actual semantics; same
counter, accurate meaning. Avoids breaking any caller that already
relies on fireCount as a total.
3. src/core/triggers.ts:fireWake β mid-run prompt says "Status: fired"
even when terminal=false, which can mislead downstream handling
into treating an in-flight watcher as a completed run. Decoupled
the prompt-display status from the on-disk TriggerStatus enum:
non-terminal fires now show "Status: signalled" in both the header
and the "Status:" line. Terminal fires unchanged.
4. src/core/triggers.ts:bufferAsPayload + fireWake β FIRE_PAYLOAD_MAX_BYTES
truncation used String.prototype.slice (UTF-16 code units), so a
payload of N multi-byte characters could exceed the documented byte
cap and split a character mid-codepoint. Introduced truncateUtf8Tail/
truncateUtf8Head helpers that encode to UTF-8 bytes, slice on a byte
boundary, then walk over UTF-8 continuation bytes (10xxxxxx) so we
never cut a multi-byte sequence in half. The byte cap is now actually
enforced as bytes.
5. src/storage/trigger-store.ts:282 + supervisor β updateTrigger only
marks the store dirty (10s autosave window). For terminal status
transitions (errored/cancelled/timed_out/fired) a crash inside that
window would leave on-disk status as "running", which loadTriggers()
then misclassifies as "terminated by previous restart". Added
persistNow() calls at the four supervisor transition sites:
failTrigger, cancelTrigger, the hard-timeout handler, and the
finalizeExit path. The on-disk store now always agrees with the
in-memory state across terminal transitions.
6. src/core/gateway-actions.ts:trigger_create β always returned
"Status: running" even when spawnTrigger() failed without throwing
(unsupported language slipping past validation, child.pid undefined,
failTrigger() routing). With #5 above, the store now reflects the
real state synchronously, so we re-read getTrigger(id) after spawn
and return ok:false + lastError when it landed in "errored", or
surface the actual status string otherwise. Callers no longer get
false success responses.
Regression tests (triggers-extended.test.ts):
- mid-run prompt asserts "Status: signalled" appears and "Status: fired"
does NOT, on a still-running trigger. Covers fix #3.
- multi-byte UTF-8 payload (2000Γ π§ = 8000 bytes vs 4000 string length)
asserts the prompt's byte length stays bounded AND contains no
Unicode replacement character (U+FFFD), which would indicate a
mid-codepoint split. Covers fix #4.
The remaining Copilot thread (introducing a dedicated non-terminal
status enum value, replacing the TriggerStatus|"signalled" display-
status hack) is a wider type change with API/store implications;
better tackled in a follow-up than bundled into a defensive sweep.
Verification:
- npx vitest run β 1747/1748 pass (the 1 failure is the pre-existing
package.functional "Stopped" expectation that fails whenever a live
Talon daemon is running on the host β same env-dependent failure
noted in PR #144 and PR #90's verification blocks).
- npx vitest run src/__tests__/triggers* src/__tests__/trigger-store* β
74/74 pass (35 in triggers-extended including 2 new, 22 in
trigger-store, 17 in triggers).
- npx tsc --noEmit β clean.
- npx prettier --check on all 4 changed files β clean.
- npm run lint β 0 errors in changed files (10 pre-existing warnings
elsewhere unchanged).
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claudiusthebot <claudius@anthropic.com>
4 tasks
dylanneve1
pushed a commit
that referenced
this pull request
May 19, 2026
β¦lper (#196) Four handlers (claude-sdk, kilo, opencode, codex) inlined the same ~30-line `classify` β `classifyRetry` β `reset_and_retry` / `fallback_model` / `propagate` block. Same logic, same shape, only the log-line prefix differed. This was item #4-shaped duplication called out in the May 16 backend review. ## What's shared now New `src/backend/shared/handle-retry.ts` exports `applyRetryDecision({err, chatId, activeModel, retried, params, recurseWithRetried, backendLabel?, resetNoun?})`. Owns: - `classify(err)` + per-reason metric increment - `classifyRetry` decision - `reset_and_retry` β `resetSession` + recurse - `fallback_model` β transient `setChatModel` + recurse + restore in `finally` - `propagate` β return classified error for caller to throw Returns `{retry?, classified}` so each handler stays in charge of its own throw/return β the helper just owns the recovery side effects. ## Each backend Four handlers drop their inline decision block + the now-unused `classify` / `classifyRetry` / `resetSession` / `setChatModel` / `getChatSettings` imports. Codex preserves its own `maybeFallbackForChatGptMismatch` helper above this call site (backend-specific recovery runs FIRST). Codex also opts into `resetNoun: "thread"` for the log line; others use the default `"session"`. ## Tests New `src/__tests__/shared-handle-retry.test.ts` (8 tests): - propagate when err non-retryable - propagate when already retried - reset+recurse on session_expired (verifies session reset) - reset+recurse on context_length - fallback-model transient swap (verifies model active during recursion + restored after) - fallback-model restores even when recursion throws - propagate when retryable but no fallback registered - smoke test: omitted backendLabel doesn't throw Existing handler-level integration tests (codex-handler covers all four recovery paths end-to-end; kilo/opencode have their own coverage) all still pass β the helper is exercised through them too. ## Verified - `npx tsc --noEmit` clean - `npm test` β 2404 passing, 12 skipped (live), 0 failing (was 2396; +8 new helper tests) - `npm run lint` β 11 warnings, 0 errors (all pre-existing in `src/frontend/discord/`) - `npm run format:check` clean ## Net impact -88 LOC of duplicated handler code, +142 LOC of shared helper + 291 LOC of tests. The duplication delta is the win: future retry-logic tweaks land in one file instead of four.
Closed
3 tasks
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.
Bumps actions/upload-artifact from 4 to 7.
Release notes
Sourced from actions/upload-artifact's releases.
... (truncated)
Commits
bbbca2dSupport direct file uploads (#764)589182cUpgrade the module to ESM and bump dependencies (#762)47309c9Merge pull request #754 from actions/Link-/add-proxy-integration-tests02a8460Add proxy integration testb7c566aMerge pull request #745 from actions/upload-artifact-v6-releasee516bc8docs: correct description of Node.js 24 support in READMEddc45eddocs: update README to correct action name for Node.js 24 support615b319chore: release v6.0.0 for Node.js 24 support017748bMerge pull request #744 from actions/fix-storage-blob38d4c79chore: rebuild distDependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)