@gajae-code/coding-agent 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -1
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +54 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +38 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/src/async/job-manager.ts +78 -20
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +51 -0
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-runtime.ts +411 -17
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
- package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
- package/src/gjc-runtime/workflow-manifest.ts +4 -68
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/modes/DESIGN.md +51 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +7 -0
- package/src/modes/controllers/selector-controller.ts +45 -0
- package/src/modes/runtime-init.ts +3 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session.ts +88 -9
- package/src/session/agent-session.ts +822 -172
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -44,13 +44,13 @@ Use it only for sanctioned GJC workflow CLI persistence or state read/write/cont
|
|
|
44
44
|
{{else}}
|
|
45
45
|
- Use bash only for terminal operations that dedicated tools do not cover.
|
|
46
46
|
{{/when}}
|
|
47
|
-
- Never pipe through `| head -n N` or `| tail -n N` — output is already truncated
|
|
47
|
+
- Never pipe through `| head -n N` or `| tail -n N` — output is already truncated. Recover omitted output only when the result includes an `artifact://<id>` footer or metadata reference; truncation without a reference leaves the visible output incomplete with no recoverable artifact.
|
|
48
48
|
- Never redirect with `2>&1` or `2>/dev/null` — stdout and stderr are already merged.
|
|
49
49
|
</critical>
|
|
50
50
|
|
|
51
51
|
<output>
|
|
52
52
|
- Returns output and exit code.
|
|
53
|
-
- Truncated output is
|
|
53
|
+
- Truncated output is recoverable only when the result includes an `artifact://<id>` footer or metadata reference; truncation evidence without such a reference means the visible output is incomplete and no artifact is recoverable.
|
|
54
54
|
- Exit codes shown on non-zero exit
|
|
55
55
|
</output>
|
|
56
56
|
|
|
@@ -64,6 +64,6 @@ Use it only for sanctioned GJC workflow CLI persistence or state read/write/cont
|
|
|
64
64
|
|
|
65
65
|
# Output minimizer
|
|
66
66
|
|
|
67
|
-
- Bash stdout/stderr may be rewritten before you see it: long output
|
|
68
|
-
- When the minimizer changes
|
|
69
|
-
-
|
|
67
|
+
- Bash stdout/stderr may be rewritten before you see it: long output keeps only the last 1 KiB by default to reduce noise and input-token use. Explicit `tools.artifactTailBytes` / `tools.artifactHeadBytes` settings can set the tail budget or retain both ends. Prefer focused commands and dedicated `search`/`find` tools over producing broad output. Test/lint runners (e.g. `bun test`, `cargo test`, ESLint) are also passed through heuristic filters that drop noise and keep failures.
|
|
68
|
+
- When the local minimizer changes visible text, successful artifact storage appends a footer containing an `artifact://<id>` reference. Complete artifacts are labeled as full output; hard-capped artifacts report omitted bytes instead. If artifact allocation/storage is unavailable before a writer/save operation is attempted, truncation may have no reference or diagnostic. If an artifact writer/save operation is attempted and fails, a bounded diagnostic is emitted without inventing an artifact URI.
|
|
69
|
+
- ACP/client-terminal output can arrive already truncated from the beginning. Treat any truncation notice or metadata as evidence that the visible tail is incomplete. Recover omitted output only when an `artifact://` footer or metadata reference is present; truncation evidence without a reference means the visible output is incomplete and no artifact is recoverable. Output with neither truncation evidence nor an artifact reference is the complete emitted output.
|
|
@@ -12,6 +12,11 @@ Behavior:
|
|
|
12
12
|
- Repeated searches add to the active tool set; they do not remove earlier selections
|
|
13
13
|
- Newly activated tools become available before the next model call in the same overall turn
|
|
14
14
|
|
|
15
|
+
Follow-through:
|
|
16
|
+
- Activation only changes the active tool set; it does not execute a discovered tool or complete its work.
|
|
17
|
+
- If the task still needs a newly activated capability, call that tool in the next model turn. Do not claim that a browser action, web search, integration, or subagent ran until its tool result is present.
|
|
18
|
+
- If discovery was the only requested action, report availability as availability, not as completed work.
|
|
19
|
+
|
|
15
20
|
Not for repository/file/code search. Tool discovery only.
|
|
16
21
|
|
|
17
22
|
Returns JSON with:
|
|
@@ -28,7 +28,7 @@ Subagents have no conversation history. Every fact, file path, and direction the
|
|
|
28
28
|
{{#if independentMode}}- `.inheritContext`: independent mode cannot inherit parent conversation. Omit it or set `"none"`; any non-`none` value is rejected before scheduling.{{/if}}
|
|
29
29
|
{{#if customSchemaEnabled}}- `schema`: JTD schema for expected structured output (do not put format rules in assignments){{/if}}
|
|
30
30
|
- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.
|
|
31
|
-
{{#if isolationEnabled}}- `isolated`: run in isolated
|
|
31
|
+
{{#if isolationEnabled}}- `isolated`: run in an isolated environment; REQUIRED when the user explicitly requests a worktree (for example, "use worktree"), and use when tasks edit overlapping files{{/if}}
|
|
32
32
|
</parameters>
|
|
33
33
|
|
|
34
34
|
<rules>
|
|
@@ -18,6 +18,10 @@ import type { ImageContent, MessageAttribution } from "@gajae-code/ai";
|
|
|
18
18
|
import { getConfigRootDir, isEnoent } from "@gajae-code/utils";
|
|
19
19
|
import { withFileLock } from "../config/file-lock";
|
|
20
20
|
import type { CustomMessage } from "../session/messages";
|
|
21
|
+
import { type GhResult, type RunGh, runGhDefault } from "../utils/gh";
|
|
22
|
+
|
|
23
|
+
export type { GhResult, RunGh };
|
|
24
|
+
export { runGhDefault };
|
|
21
25
|
|
|
22
26
|
export const STAR_REMINDER_REPO = "Yeachan-Heo/gajae-code";
|
|
23
27
|
export const STAR_REMINDER_CUSTOM_TYPE = "star-reminder";
|
|
@@ -34,15 +38,6 @@ export interface StarReminderState {
|
|
|
34
38
|
|
|
35
39
|
export type StarCheckStatus = "starred" | "unstarred" | "unavailable";
|
|
36
40
|
|
|
37
|
-
export interface GhResult {
|
|
38
|
-
exitCode: number;
|
|
39
|
-
stdout: string;
|
|
40
|
-
stderr: string;
|
|
41
|
-
timedOut?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type RunGh = (args: string[], options?: { timeoutMs?: number }) => Promise<GhResult>;
|
|
45
|
-
|
|
46
41
|
export interface StarReminderDeps {
|
|
47
42
|
statePath?: string;
|
|
48
43
|
now?: () => Date;
|
|
@@ -187,39 +182,6 @@ export async function recordDeclinedAfterNo(deps?: StarReminderDeps): Promise<St
|
|
|
187
182
|
}, deps);
|
|
188
183
|
}
|
|
189
184
|
|
|
190
|
-
// --------------------------------------------------------------------------
|
|
191
|
-
// gh helpers
|
|
192
|
-
// --------------------------------------------------------------------------
|
|
193
|
-
|
|
194
|
-
/** Default `gh` runner. Returns an unavailable-style result instead of throwing. */
|
|
195
|
-
export async function runGhDefault(args: string[], options?: { timeoutMs?: number }): Promise<GhResult> {
|
|
196
|
-
const ghPath = Bun.which("gh");
|
|
197
|
-
if (!ghPath) {
|
|
198
|
-
return { exitCode: -1, stdout: "", stderr: "gh not found", timedOut: false };
|
|
199
|
-
}
|
|
200
|
-
const timeoutMs = options?.timeoutMs ?? GH_TIMEOUT_MS;
|
|
201
|
-
let timedOut = false;
|
|
202
|
-
try {
|
|
203
|
-
const proc = Bun.spawn([ghPath, ...args], { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
|
|
204
|
-
const timer = setTimeout(() => {
|
|
205
|
-
timedOut = true;
|
|
206
|
-
proc.kill();
|
|
207
|
-
}, timeoutMs);
|
|
208
|
-
try {
|
|
209
|
-
const [stdout, stderr] = await Promise.all([
|
|
210
|
-
new Response(proc.stdout).text(),
|
|
211
|
-
new Response(proc.stderr).text(),
|
|
212
|
-
]);
|
|
213
|
-
const exitCode = await proc.exited;
|
|
214
|
-
return { exitCode, stdout, stderr, timedOut };
|
|
215
|
-
} finally {
|
|
216
|
-
clearTimeout(timer);
|
|
217
|
-
}
|
|
218
|
-
} catch (err) {
|
|
219
|
-
return { exitCode: -1, stdout: "", stderr: err instanceof Error ? err.message : String(err), timedOut };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
185
|
function resolveRunGh(deps?: StarReminderDeps): RunGh {
|
|
224
186
|
return deps?.runGh ?? runGhDefault;
|
|
225
187
|
}
|
|
@@ -75,6 +75,7 @@ const STARTUP_TIMEOUT_GRACE_MS = 500;
|
|
|
75
75
|
* budget derived from the readiness deadline (see `maxStartupTimeoutMs`).
|
|
76
76
|
*/
|
|
77
77
|
const MAX_STARTUP_TIMEOUT_MS = 1_750;
|
|
78
|
+
const DEFAULT_EXACT_CONFIG_STARTUP_TIMEOUT_MS = 30_000;
|
|
78
79
|
|
|
79
80
|
export function resolveStartupTimeoutMs(configs: MCPServerConfig[], maxStartupTimeoutMs?: number): number {
|
|
80
81
|
const ceiling =
|
|
@@ -88,6 +89,17 @@ export function resolveStartupTimeoutMs(configs: MCPServerConfig[], maxStartupTi
|
|
|
88
89
|
return Math.min(ceiling, Math.max(STARTUP_TIMEOUT_MS, Math.max(...configuredTimeouts) + STARTUP_TIMEOUT_GRACE_MS));
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
export function resolveExactConfigStartupTimeoutMs(configs: MCPServerConfig[]): number {
|
|
93
|
+
const effectiveTimeouts = configs.map(config => {
|
|
94
|
+
const timeout = config.timeout;
|
|
95
|
+
return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0
|
|
96
|
+
? timeout
|
|
97
|
+
: DEFAULT_EXACT_CONFIG_STARTUP_TIMEOUT_MS;
|
|
98
|
+
});
|
|
99
|
+
if (effectiveTimeouts.length === 0) return STARTUP_TIMEOUT_MS;
|
|
100
|
+
return Math.max(...effectiveTimeouts) + STARTUP_TIMEOUT_GRACE_MS;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
function trackPromise<T>(promise: Promise<T>): TrackedPromise<T> {
|
|
92
104
|
const tracked: TrackedPromise<T> = { promise, status: "pending" };
|
|
93
105
|
promise.then(
|
|
@@ -650,10 +662,13 @@ export class MCPManager {
|
|
|
650
662
|
}
|
|
651
663
|
|
|
652
664
|
if (connectionTasks.length > 0) {
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
665
|
+
const configs = connectionTasks.map(task => task.config);
|
|
666
|
+
// An exact config is explicit operator intent, so its declared connection
|
|
667
|
+
// windows govern startup. Ordinary discovery keeps the short ceiling.
|
|
668
|
+
const startupTimeoutMs =
|
|
669
|
+
this.#toolsOnly && this.#maxStartupTimeoutMs === undefined
|
|
670
|
+
? resolveExactConfigStartupTimeoutMs(configs)
|
|
671
|
+
: resolveStartupTimeoutMs(configs, this.#maxStartupTimeoutMs);
|
|
657
672
|
const firstUnexpectedFailure = Promise.withResolvers<{ reason: unknown }>();
|
|
658
673
|
if (this.#toolsOnly) {
|
|
659
674
|
for (const task of connectionTasks) {
|
package/src/sdk/acp/adapter.ts
CHANGED
|
@@ -8,7 +8,7 @@ type JsonObject = Record<string, unknown>;
|
|
|
8
8
|
|
|
9
9
|
/** The small agent-side ACP surface used for reverse requests. */
|
|
10
10
|
export interface AcpReverseConnection {
|
|
11
|
-
request?(method: string, params: JsonObject): Promise<unknown>;
|
|
11
|
+
request?(method: string, params: JsonObject, options?: { cancellationSignal?: AbortSignal }): Promise<unknown>;
|
|
12
12
|
[key: string]: unknown;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -42,6 +42,7 @@ export type AcpReconnectFailedHandler = (error: SdkClientError) => void;
|
|
|
42
42
|
export type AcpFrameHandler = (frame: SdkFrame) => void;
|
|
43
43
|
type ReverseRequest = {
|
|
44
44
|
state: "pending" | "cancelled";
|
|
45
|
+
controller?: AbortController;
|
|
45
46
|
cancelTimer?: NodeJS.Timeout;
|
|
46
47
|
};
|
|
47
48
|
|
|
@@ -142,7 +143,10 @@ export class AcpSdkAdapter {
|
|
|
142
143
|
if (this.#closed) return;
|
|
143
144
|
this.#closed = true;
|
|
144
145
|
if (this.#heartbeat) clearInterval(this.#heartbeat);
|
|
145
|
-
for (const request of this.#reverseRequests.values())
|
|
146
|
+
for (const request of this.#reverseRequests.values()) {
|
|
147
|
+
request.controller?.abort();
|
|
148
|
+
if (request.cancelTimer) clearTimeout(request.cancelTimer);
|
|
149
|
+
}
|
|
146
150
|
this.#reverseRequests.clear();
|
|
147
151
|
this.#unsubscribe?.();
|
|
148
152
|
this.#unsubscribeReconnect?.();
|
|
@@ -263,6 +267,7 @@ export class AcpSdkAdapter {
|
|
|
263
267
|
if (this.#closed || !this.#providers.length) return;
|
|
264
268
|
if (this.#reclaiming) return await this.#reclaiming;
|
|
265
269
|
this.#reclaiming = (async () => {
|
|
270
|
+
this.#abortActiveReverseRequests();
|
|
266
271
|
this.#connectionId = this.#client.connectionId;
|
|
267
272
|
if (!this.#connectionId) this.#connectionId = await this.#waitForConnectionId();
|
|
268
273
|
for (const provider of this.#providers) await this.registerProvider(provider);
|
|
@@ -349,13 +354,14 @@ export class AcpSdkAdapter {
|
|
|
349
354
|
!this.#ownsReverseLease(connectionId, capability, leaseId)
|
|
350
355
|
)
|
|
351
356
|
return;
|
|
352
|
-
const
|
|
357
|
+
const controller = new AbortController();
|
|
358
|
+
const active: ReverseRequest = { state: "pending", controller };
|
|
353
359
|
this.#reverseRequests.set(id, active);
|
|
354
360
|
try {
|
|
355
361
|
const request = frame.payload as JsonObject | undefined;
|
|
356
362
|
const method = typeof request?.method === "string" ? request.method : "";
|
|
357
363
|
const payload = request?.payload && typeof request.payload === "object" ? (request.payload as JsonObject) : {};
|
|
358
|
-
const result = await this.#forwardReverse(method, payload);
|
|
364
|
+
const result = await this.#forwardReverse(method, payload, controller.signal);
|
|
359
365
|
if (!this.#canRespondToReverse(id, active, connectionId, capability, leaseId)) return;
|
|
360
366
|
|
|
361
367
|
this.#client.send({ type: "reverse_response", id, connectionId, leaseId, ok: true, result });
|
|
@@ -404,6 +410,7 @@ export class AcpSdkAdapter {
|
|
|
404
410
|
const request: ReverseRequest = this.#reverseRequests.get(id) ?? { state: "pending" };
|
|
405
411
|
if (request.state === "cancelled") return;
|
|
406
412
|
request.state = "cancelled";
|
|
413
|
+
request.controller?.abort();
|
|
407
414
|
request.cancelTimer = setTimeout(() => this.#finishReverse(id, request), this.#reverseCancelTtlMs);
|
|
408
415
|
this.#reverseRequests.set(id, request);
|
|
409
416
|
}
|
|
@@ -414,9 +421,18 @@ export class AcpSdkAdapter {
|
|
|
414
421
|
this.#reverseRequests.delete(id);
|
|
415
422
|
}
|
|
416
423
|
|
|
417
|
-
|
|
424
|
+
#abortActiveReverseRequests(): void {
|
|
425
|
+
for (const [id, request] of this.#reverseRequests) {
|
|
426
|
+
request.state = "cancelled";
|
|
427
|
+
request.controller?.abort();
|
|
428
|
+
this.#finishReverse(id, request);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async #forwardReverse(method: string, payload: JsonObject, signal: AbortSignal): Promise<unknown> {
|
|
418
433
|
if (!method || !this.#connection) throw new AcpSdkAdapterError("acp_reverse_unavailable");
|
|
419
|
-
if (this.#connection.request)
|
|
434
|
+
if (this.#connection.request)
|
|
435
|
+
return await this.#connection.request(method, payload, { cancellationSignal: signal });
|
|
420
436
|
const target = this.#connection[method];
|
|
421
437
|
if (typeof target !== "function")
|
|
422
438
|
throw new AcpSdkAdapterError("acp_reverse_unsupported", `ACP client does not support ${method}.`);
|
|
@@ -14,6 +14,7 @@ const BROKER_OPERATIONS = new Set([
|
|
|
14
14
|
"session.resume",
|
|
15
15
|
"session.close",
|
|
16
16
|
"session.delete",
|
|
17
|
+
"broker.shutdown",
|
|
17
18
|
]);
|
|
18
19
|
type RequestInput = Record<string, unknown>;
|
|
19
20
|
type BrokerRequest = {
|
|
@@ -32,8 +33,15 @@ function tokenMatches(expected: string, actual: string | null): boolean {
|
|
|
32
33
|
function isInput(value: unknown): value is RequestInput {
|
|
33
34
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
35
|
}
|
|
35
|
-
function send(socket: ServerWebSocket<unknown>, frame: Record<string, unknown>):
|
|
36
|
-
socket.send(JSON.stringify(frame));
|
|
36
|
+
function send(socket: ServerWebSocket<unknown>, frame: Record<string, unknown>): number {
|
|
37
|
+
return socket.send(JSON.stringify(frame));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type BrokerShutdownSendAction = "dropped" | "wait_for_drain" | "close";
|
|
41
|
+
|
|
42
|
+
export function brokerShutdownSendAction(status: number): BrokerShutdownSendAction {
|
|
43
|
+
if (status === 0) return "dropped";
|
|
44
|
+
return status < 0 ? "wait_for_drain" : "close";
|
|
37
45
|
}
|
|
38
46
|
function sendError(socket: ServerWebSocket<unknown>, id: string | undefined, code: string, message: string): void {
|
|
39
47
|
send(socket, { type: "broker_response", ...(id === undefined ? {} : { id }), ok: false, error: { code, message } });
|
|
@@ -45,6 +53,8 @@ export class BrokerTransport {
|
|
|
45
53
|
readonly #requestedPort: number;
|
|
46
54
|
#server: Bun.Server<undefined> | null = null;
|
|
47
55
|
#port = 0;
|
|
56
|
+
readonly #shutdownBackpressured = new WeakSet<ServerWebSocket<unknown>>();
|
|
57
|
+
#shutdownRequested = false;
|
|
48
58
|
constructor(broker: Broker, token: string, port = 0) {
|
|
49
59
|
this.#broker = broker;
|
|
50
60
|
this.#token = token;
|
|
@@ -71,8 +81,13 @@ export class BrokerTransport {
|
|
|
71
81
|
},
|
|
72
82
|
websocket: {
|
|
73
83
|
maxPayloadLength: MAX_BROKER_JSON_FRAME_BYTES * 2,
|
|
74
|
-
open: socket =>
|
|
84
|
+
open: socket => {
|
|
85
|
+
send(socket, { type: "broker_hello", protocolVersion: PROTOCOL_VERSION });
|
|
86
|
+
},
|
|
75
87
|
message: (socket, message) => void this.#handleMessage(socket, message),
|
|
88
|
+
drain: socket => {
|
|
89
|
+
if (this.#shutdownBackpressured.delete(socket)) this.#scheduleStopAfterShutdownResponse();
|
|
90
|
+
},
|
|
76
91
|
},
|
|
77
92
|
});
|
|
78
93
|
this.#port = this.#server.port ?? 0;
|
|
@@ -84,6 +99,10 @@ export class BrokerTransport {
|
|
|
84
99
|
if (server) await server.stop(true);
|
|
85
100
|
}
|
|
86
101
|
async #handleMessage(socket: ServerWebSocket<unknown>, raw: string | Buffer): Promise<void> {
|
|
102
|
+
if (this.#shutdownRequested) {
|
|
103
|
+
sendError(socket, undefined, "unavailable", "broker is shutting down");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
87
106
|
if (Buffer.byteLength(raw) > MAX_BROKER_JSON_FRAME_BYTES) {
|
|
88
107
|
sendError(socket, undefined, "payload_too_large", "broker JSON frame exceeds 4 MiB limit");
|
|
89
108
|
return;
|
|
@@ -115,6 +134,16 @@ export class BrokerTransport {
|
|
|
115
134
|
sendError(socket, frame.id, "invalid_input", "idempotencyKey must be a string");
|
|
116
135
|
return;
|
|
117
136
|
}
|
|
137
|
+
if (frame.operation === "broker.shutdown") {
|
|
138
|
+
const action = brokerShutdownSendAction(
|
|
139
|
+
send(socket, { type: "broker_response", id: frame.id, ok: true, result: { accepted: true } }),
|
|
140
|
+
);
|
|
141
|
+
if (action === "dropped") return;
|
|
142
|
+
this.#shutdownRequested = true;
|
|
143
|
+
if (action === "wait_for_drain") this.#shutdownBackpressured.add(socket);
|
|
144
|
+
else this.#scheduleStopAfterShutdownResponse();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
118
147
|
try {
|
|
119
148
|
const result = await this.#broker.handleRequest(frame.operation, frame.input, frame.idempotencyKey);
|
|
120
149
|
send(socket, { type: "broker_response", id: frame.id, ...result });
|
|
@@ -122,4 +151,7 @@ export class BrokerTransport {
|
|
|
122
151
|
sendError(socket, frame.id, "unavailable", "broker request failed");
|
|
123
152
|
}
|
|
124
153
|
}
|
|
154
|
+
#scheduleStopAfterShutdownResponse(): void {
|
|
155
|
+
setTimeout(() => void this.#broker.stop(), 25);
|
|
156
|
+
}
|
|
125
157
|
}
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/** Delivers a local file to the session's Telegram chat. */
|
|
8
|
-
export type TelegramFileSink = (file: {
|
|
8
|
+
export type TelegramFileSink = (file: {
|
|
9
|
+
path: string;
|
|
10
|
+
caption?: string;
|
|
11
|
+
mime?: string;
|
|
12
|
+
}) => Promise<{ ok: boolean; error?: string }>;
|
|
9
13
|
|
|
10
14
|
const sinks = new Map<string, TelegramFileSink>();
|
|
11
15
|
|
|
@@ -60,27 +60,10 @@ async function loadConfig(agentDir: string, kind: ChatDaemonKind): Promise<ChatD
|
|
|
60
60
|
const config = notificationConfigFromFile(loaded.value);
|
|
61
61
|
if (!config.enabled) return undefined;
|
|
62
62
|
if (kind === "discord") {
|
|
63
|
-
if (
|
|
64
|
-
!isDiscordConfigured({
|
|
65
|
-
...config,
|
|
66
|
-
sessionScope: "all",
|
|
67
|
-
idleTimeoutMs: 60_000,
|
|
68
|
-
rich: { enabled: true },
|
|
69
|
-
richDraft: { enabled: false },
|
|
70
|
-
toolActivity: { enabled: false },
|
|
71
|
-
topics: { nameTemplate: undefined },
|
|
72
|
-
btw: { enabled: true },
|
|
73
|
-
streaming: { enabled: true },
|
|
74
|
-
})
|
|
75
|
-
) {
|
|
63
|
+
if (!isDiscordConfigured(config)) {
|
|
76
64
|
throw new Error("Discord notifications are enabled but configuration is incomplete");
|
|
77
65
|
}
|
|
78
|
-
const discord = config.discord
|
|
79
|
-
botToken: string;
|
|
80
|
-
applicationId: string;
|
|
81
|
-
guildId: string;
|
|
82
|
-
parentChannelId: string;
|
|
83
|
-
};
|
|
66
|
+
const discord = config.discord;
|
|
84
67
|
const { botToken, applicationId, guildId, parentChannelId } = discord;
|
|
85
68
|
const identity = crypto
|
|
86
69
|
.createHash("sha256")
|
|
@@ -95,28 +78,10 @@ async function loadConfig(agentDir: string, kind: ChatDaemonKind): Promise<ChatD
|
|
|
95
78
|
presentation: { redact: config.redact, verbosity: config.verbosity },
|
|
96
79
|
};
|
|
97
80
|
}
|
|
98
|
-
if (
|
|
99
|
-
!isSlackConfigured({
|
|
100
|
-
...config,
|
|
101
|
-
sessionScope: "all",
|
|
102
|
-
idleTimeoutMs: 60_000,
|
|
103
|
-
rich: { enabled: true },
|
|
104
|
-
richDraft: { enabled: false },
|
|
105
|
-
toolActivity: { enabled: false },
|
|
106
|
-
topics: { nameTemplate: undefined },
|
|
107
|
-
btw: { enabled: true },
|
|
108
|
-
streaming: { enabled: true },
|
|
109
|
-
})
|
|
110
|
-
) {
|
|
81
|
+
if (!isSlackConfigured(config)) {
|
|
111
82
|
throw new Error("Slack notifications are enabled but configuration is incomplete");
|
|
112
83
|
}
|
|
113
|
-
const slack = config.slack
|
|
114
|
-
botToken: string;
|
|
115
|
-
appToken: string;
|
|
116
|
-
workspaceId: string;
|
|
117
|
-
channelId: string;
|
|
118
|
-
authorizedUserId?: string;
|
|
119
|
-
};
|
|
84
|
+
const slack = config.slack;
|
|
120
85
|
const { botToken, appToken, workspaceId, channelId, authorizedUserId } = slack;
|
|
121
86
|
const identity = crypto
|
|
122
87
|
.createHash("sha256")
|
|
@@ -35,11 +35,14 @@ export type ChatDaemonAction = "stop" | "reload";
|
|
|
35
35
|
* when deleting an observed owner-lock lease. Generation 12 refreshes retained
|
|
36
36
|
* native path and process authority semantics. Generation 13 rejects special
|
|
37
37
|
* files before retained native authority opens. Generation 14 reloads shared
|
|
38
|
-
* chat daemons after notification configuration parsing changes.
|
|
38
|
+
* chat daemons after notification configuration parsing changes. Generation 15
|
|
39
|
+
* applies the current notification configuration directly when starting chat
|
|
40
|
+
* daemon transports. Generation 16 applies Telegram sound-policy configuration
|
|
41
|
+
* through shared notification parsing.
|
|
39
42
|
*/
|
|
40
43
|
export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
|
|
41
|
-
discord:
|
|
42
|
-
slack:
|
|
44
|
+
discord: 16,
|
|
45
|
+
slack: 16,
|
|
43
46
|
};
|
|
44
47
|
|
|
45
48
|
export function chatDaemonGeneration(kind: ChatDaemonKind): number {
|
package/src/sdk/bus/config.ts
CHANGED
|
@@ -55,6 +55,7 @@ export interface NotificationSettingsSnapshot {
|
|
|
55
55
|
botToken?: string;
|
|
56
56
|
chatId?: string;
|
|
57
57
|
activation?: Record<string, unknown>;
|
|
58
|
+
sound: "all" | "important" | "none";
|
|
58
59
|
btw: {
|
|
59
60
|
enabled: boolean;
|
|
60
61
|
};
|
|
@@ -160,6 +161,11 @@ export function parseNotificationSettingsSnapshot(rawConfig?: unknown): Notifica
|
|
|
160
161
|
botToken: notificationSettingsString(telegram.botToken),
|
|
161
162
|
chatId: notificationSettingsString(telegram.chatId),
|
|
162
163
|
...(Object.keys(activation).length === 0 ? {} : { activation }),
|
|
164
|
+
sound: notificationSettingsChoice<"all" | "important" | "none">(telegram.sound, "all", [
|
|
165
|
+
"all",
|
|
166
|
+
"important",
|
|
167
|
+
"none",
|
|
168
|
+
]),
|
|
163
169
|
btw: {
|
|
164
170
|
enabled: notificationSettingsBoolean(btw.enabled, true),
|
|
165
171
|
},
|
|
@@ -228,6 +234,7 @@ export interface NotificationConfig {
|
|
|
228
234
|
* (those carrying {@link SPAWN_PROVENANCE_ENV}) unless they explicitly opt in.
|
|
229
235
|
*/
|
|
230
236
|
sessionScope: "all" | "primary";
|
|
237
|
+
sound: "all" | "important" | "none";
|
|
231
238
|
idleTimeoutMs: number;
|
|
232
239
|
btw: {
|
|
233
240
|
enabled: boolean;
|
|
@@ -270,6 +277,7 @@ export function getNotificationConfig(settings: NotificationSettingsReader): Not
|
|
|
270
277
|
verbosity: snapshot.verbosity,
|
|
271
278
|
sessionScope: snapshot.sessionScope,
|
|
272
279
|
idleTimeoutMs: snapshot.idleTimeoutMs,
|
|
280
|
+
sound: snapshot.telegram.sound,
|
|
273
281
|
rich: snapshot.telegram.rich,
|
|
274
282
|
btw: snapshot.telegram.btw,
|
|
275
283
|
richDraft: snapshot.telegram.richDraft,
|
|
@@ -358,7 +366,10 @@ export function isTelegramConfigured(
|
|
|
358
366
|
}
|
|
359
367
|
|
|
360
368
|
/** Is Discord configured with all credentials and routing identifiers required by its daemon. */
|
|
361
|
-
export function isDiscordConfigured(cfg: NotificationConfig): cfg is
|
|
369
|
+
export function isDiscordConfigured(cfg: Pick<NotificationConfig, "enabled" | "discord">): cfg is Pick<
|
|
370
|
+
NotificationConfig,
|
|
371
|
+
"enabled"
|
|
372
|
+
> & {
|
|
362
373
|
discord: { botToken: string; applicationId: string; guildId: string; parentChannelId: string };
|
|
363
374
|
} {
|
|
364
375
|
return (
|
|
@@ -371,7 +382,10 @@ export function isDiscordConfigured(cfg: NotificationConfig): cfg is Notificatio
|
|
|
371
382
|
}
|
|
372
383
|
|
|
373
384
|
/** Is Slack configured with both SDK tokens and its workspace/channel routing identifiers. */
|
|
374
|
-
export function isSlackConfigured(cfg: NotificationConfig): cfg is
|
|
385
|
+
export function isSlackConfigured(cfg: Pick<NotificationConfig, "enabled" | "slack">): cfg is Pick<
|
|
386
|
+
NotificationConfig,
|
|
387
|
+
"enabled"
|
|
388
|
+
> & {
|
|
375
389
|
slack: { botToken: string; appToken: string; workspaceId: string; channelId: string };
|
|
376
390
|
} {
|
|
377
391
|
return (
|
|
@@ -539,6 +553,8 @@ export interface RedactableAction {
|
|
|
539
553
|
workflowGateId?: string;
|
|
540
554
|
question?: string;
|
|
541
555
|
options?: string[];
|
|
556
|
+
/** Selected zero-based option positions for transport-specific multi-select rendering. */
|
|
557
|
+
selectedOptionIndices?: number[];
|
|
542
558
|
summary?: string;
|
|
543
559
|
/** Optional zero-based recommendation into the authoritative raw options. */
|
|
544
560
|
recommendedIndex?: number;
|