@sayknow-cli/coding-agent 0.3.12 → 0.3.15
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/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +2 -2
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commands/web-search.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +4 -4
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +9 -9
- package/dist/types/commit/agentic/tools/schemas.d.ts +9 -9
- package/dist/types/commit/agentic/tools/split-commit.d.ts +9 -9
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +3 -3
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +9 -9
- package/dist/types/config/keybindings.d.ts +134 -129
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +35 -35
- package/dist/types/config/models-config-schema.d.ts +111 -111
- package/dist/types/config/settings-schema.d.ts +56 -16
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +12 -12
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/edit/modes/patch.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/goals/tools/goal-tool.d.ts +6 -6
- package/dist/types/i18n/messages/en.d.ts +8 -8
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/lsp/index.d.ts +9 -9
- package/dist/types/lsp/types.d.ts +9 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/skc-runtime/state-schema.d.ts +19 -19
- package/dist/types/skc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/task/agents.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +35 -27
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/browser.d.ts +28 -28
- package/dist/types/tools/computer.d.ts +20 -20
- package/dist/types/tools/cron.d.ts +2 -2
- package/dist/types/tools/debug.d.ts +40 -40
- package/dist/types/tools/gh.d.ts +10 -10
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/monitor.d.ts +6 -6
- package/dist/types/tools/skill-discovery.d.ts +4 -4
- package/dist/types/tools/subagent.d.ts +12 -12
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/tools/todo-write.d.ts +8 -8
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/dist/types/web/insane/bridge.d.ts +9 -9
- package/dist/types/web/search/index.d.ts +4 -4
- package/package.json +8 -8
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2958 -430
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/skc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/sayknow-pet-widget.ts +360 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/skc-runtime/session-state-sidecar.ts +756 -135
- package/src/skc-runtime/tmux-common.ts +26 -7
- package/src/skc-runtime/tmux-gc.ts +52 -9
- package/src/skc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/skc-runtime/tmux-sessions.ts +871 -68
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
|
@@ -4,13 +4,13 @@ import type { AgentProgress, AgentSource } from "../task/types";
|
|
|
4
4
|
import type { ToolSession } from "./index";
|
|
5
5
|
declare const subagentSchema: z.ZodObject<{
|
|
6
6
|
action: z.ZodEnum<{
|
|
7
|
-
await: "await";
|
|
8
|
-
cancel: "cancel";
|
|
9
|
-
inspect: "inspect";
|
|
10
|
-
list: "list";
|
|
11
|
-
pause: "pause";
|
|
12
7
|
resume: "resume";
|
|
8
|
+
pause: "pause";
|
|
13
9
|
steer: "steer";
|
|
10
|
+
list: "list";
|
|
11
|
+
cancel: "cancel";
|
|
12
|
+
inspect: "inspect";
|
|
13
|
+
await: "await";
|
|
14
14
|
}>;
|
|
15
15
|
ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
16
16
|
id: z.ZodOptional<z.ZodString>;
|
|
@@ -19,9 +19,9 @@ declare const subagentSchema: z.ZodObject<{
|
|
|
19
19
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
20
20
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
21
21
|
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
22
|
+
receipt: "receipt";
|
|
22
23
|
full: "full";
|
|
23
24
|
preview: "preview";
|
|
24
|
-
receipt: "receipt";
|
|
25
25
|
}>>;
|
|
26
26
|
}, z.core.$strip>;
|
|
27
27
|
type SubagentParams = z.infer<typeof subagentSchema>;
|
|
@@ -68,13 +68,13 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
|
|
|
68
68
|
readonly description: string;
|
|
69
69
|
readonly parameters: z.ZodObject<{
|
|
70
70
|
action: z.ZodEnum<{
|
|
71
|
-
await: "await";
|
|
72
|
-
cancel: "cancel";
|
|
73
|
-
inspect: "inspect";
|
|
74
|
-
list: "list";
|
|
75
|
-
pause: "pause";
|
|
76
71
|
resume: "resume";
|
|
72
|
+
pause: "pause";
|
|
77
73
|
steer: "steer";
|
|
74
|
+
list: "list";
|
|
75
|
+
cancel: "cancel";
|
|
76
|
+
inspect: "inspect";
|
|
77
|
+
await: "await";
|
|
78
78
|
}>;
|
|
79
79
|
ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
80
80
|
id: z.ZodOptional<z.ZodString>;
|
|
@@ -83,9 +83,9 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
|
|
|
83
83
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
84
84
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
85
85
|
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
86
|
+
receipt: "receipt";
|
|
86
87
|
full: "full";
|
|
87
88
|
preview: "preview";
|
|
88
|
-
receipt: "receipt";
|
|
89
89
|
}>>;
|
|
90
90
|
}, z.core.$strip>;
|
|
91
91
|
readonly strict = true;
|
|
@@ -26,6 +26,13 @@ export declare class TelegramSendTool implements AgentTool<typeof telegramSendSc
|
|
|
26
26
|
readonly strict = true;
|
|
27
27
|
constructor(session: ToolSession);
|
|
28
28
|
static createIf(session: ToolSession): TelegramSendTool | null;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve `requested` against the workspace root and confine it via realpath:
|
|
31
|
+
* blocks absolute paths outside the project, `..` traversal, and symlinks that
|
|
32
|
+
* escape the root. Returns the resolved real path of a regular file, or an
|
|
33
|
+
* error message. This is the egress safety boundary — the model can only send
|
|
34
|
+
* files that genuinely live inside the session workspace.
|
|
35
|
+
*/
|
|
29
36
|
private resolveContainedFile;
|
|
30
37
|
execute(_toolCallId: string, params: TelegramSendParams, _signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<TelegramSendDetails>, _context?: AgentToolContext): Promise<AgentToolResult<TelegramSendDetails>>;
|
|
31
38
|
}
|
|
@@ -28,13 +28,13 @@ export interface TodoWriteToolDetails {
|
|
|
28
28
|
declare const todoWriteSchema: z.ZodObject<{
|
|
29
29
|
ops: z.ZodArray<z.ZodObject<{
|
|
30
30
|
op: z.ZodEnum<{
|
|
31
|
-
append: "append";
|
|
32
|
-
done: "done";
|
|
33
31
|
drop: "drop";
|
|
34
32
|
init: "init";
|
|
35
|
-
note: "note";
|
|
36
|
-
rm: "rm";
|
|
37
33
|
start: "start";
|
|
34
|
+
done: "done";
|
|
35
|
+
rm: "rm";
|
|
36
|
+
append: "append";
|
|
37
|
+
note: "note";
|
|
38
38
|
}>;
|
|
39
39
|
list: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
40
40
|
phase: z.ZodString;
|
|
@@ -70,13 +70,13 @@ export declare class TodoWriteTool implements AgentTool<typeof todoWriteSchema,
|
|
|
70
70
|
readonly parameters: z.ZodObject<{
|
|
71
71
|
ops: z.ZodArray<z.ZodObject<{
|
|
72
72
|
op: z.ZodEnum<{
|
|
73
|
-
append: "append";
|
|
74
|
-
done: "done";
|
|
75
73
|
drop: "drop";
|
|
76
74
|
init: "init";
|
|
77
|
-
note: "note";
|
|
78
|
-
rm: "rm";
|
|
79
75
|
start: "start";
|
|
76
|
+
done: "done";
|
|
77
|
+
rm: "rm";
|
|
78
|
+
append: "append";
|
|
79
|
+
note: "note";
|
|
80
80
|
}>;
|
|
81
81
|
list: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
82
82
|
phase: z.ZodString;
|
|
@@ -8,6 +8,8 @@ export interface OpenInEditorOptions {
|
|
|
8
8
|
/** Keep the file's trailing newline instead of trimming it from the returned text. */
|
|
9
9
|
trimTrailingNewline?: boolean;
|
|
10
10
|
}
|
|
11
|
+
/** Remove one platform-standard trailing line terminator from editor content. */
|
|
12
|
+
export declare function trimEditorTrailingNewline(text: string): string;
|
|
11
13
|
/**
|
|
12
14
|
* Opens `content` in the user's external editor and returns the edited text.
|
|
13
15
|
* Returns `null` if the editor exits with a non-zero code.
|
|
@@ -17,18 +17,18 @@ export declare const INSANE_VENDOR_DIR: string;
|
|
|
17
17
|
/** Stable note prefixes — tests assert on these without depending on full stderr. */
|
|
18
18
|
export declare const INSANE_NOTES: {
|
|
19
19
|
readonly guardBlocked: (reason: string) => string;
|
|
20
|
-
readonly vendorMissing:
|
|
21
|
-
readonly noPython:
|
|
22
|
-
readonly noCurlCffi:
|
|
23
|
-
readonly noBrowser:
|
|
20
|
+
readonly vendorMissing: "insane fallback unavailable: vendor engine missing at packages/coding-agent/vendor/insane-search";
|
|
21
|
+
readonly noPython: "insane fallback unavailable: python3 not found; install python3 and curl_cffi, then retry with web.insaneFallback=true";
|
|
22
|
+
readonly noCurlCffi: "insane fallback unavailable: python3 cannot import curl_cffi; install curl_cffi for Phase 0-2";
|
|
23
|
+
readonly noBrowser: "insane fallback unavailable: node/playwright/stealth dependencies missing for Phase 3; install dependencies under packages/coding-agent/vendor/insane-search/engine/templates";
|
|
24
24
|
readonly timeout: (seconds: number) => string;
|
|
25
|
-
readonly invalidJson:
|
|
26
|
-
readonly authRequired:
|
|
25
|
+
readonly invalidJson: "insane fallback failed: engine returned invalid JSON";
|
|
26
|
+
readonly authRequired: "insane fallback stopped: authentication required";
|
|
27
27
|
readonly verdict: (verdict: string) => string;
|
|
28
28
|
readonly untried: (routes: string) => string;
|
|
29
|
-
readonly mustBrowserMcp:
|
|
30
|
-
readonly concurrency:
|
|
31
|
-
readonly emptyContent:
|
|
29
|
+
readonly mustBrowserMcp: "insane fallback requires browser MCP/manual phase: must_invoke_playwright_mcp=true";
|
|
30
|
+
readonly concurrency: "insane fallback skipped: max concurrent engine attempts reached";
|
|
31
|
+
readonly emptyContent: "insane fallback failed: engine reported ok but returned no content";
|
|
32
32
|
};
|
|
33
33
|
/** Raw JSON envelope produced by `python3 -m engine --json`. */
|
|
34
34
|
export interface InsaneFetchResultRaw {
|
|
@@ -16,8 +16,8 @@ export declare const webSearchSchema: z.ZodObject<{
|
|
|
16
16
|
query: z.ZodString;
|
|
17
17
|
recency: z.ZodOptional<z.ZodEnum<{
|
|
18
18
|
day: "day";
|
|
19
|
-
month: "month";
|
|
20
19
|
week: "week";
|
|
20
|
+
month: "month";
|
|
21
21
|
year: "year";
|
|
22
22
|
}>>;
|
|
23
23
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
@@ -25,9 +25,9 @@ export declare const webSearchSchema: z.ZodObject<{
|
|
|
25
25
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
26
26
|
num_search_results: z.ZodOptional<z.ZodNumber>;
|
|
27
27
|
xai_search_mode: z.ZodOptional<z.ZodEnum<{
|
|
28
|
+
x: "x";
|
|
28
29
|
web: "web";
|
|
29
30
|
web_and_x: "web_and_x";
|
|
30
|
-
x: "x";
|
|
31
31
|
}>>;
|
|
32
32
|
allowed_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
33
|
excluded_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -79,8 +79,8 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
|
|
|
79
79
|
query: z.ZodString;
|
|
80
80
|
recency: z.ZodOptional<z.ZodEnum<{
|
|
81
81
|
day: "day";
|
|
82
|
-
month: "month";
|
|
83
82
|
week: "week";
|
|
83
|
+
month: "month";
|
|
84
84
|
year: "year";
|
|
85
85
|
}>>;
|
|
86
86
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
@@ -88,9 +88,9 @@ export declare class WebSearchTool implements AgentTool<typeof webSearchSchema,
|
|
|
88
88
|
temperature: z.ZodOptional<z.ZodNumber>;
|
|
89
89
|
num_search_results: z.ZodOptional<z.ZodNumber>;
|
|
90
90
|
xai_search_mode: z.ZodOptional<z.ZodEnum<{
|
|
91
|
+
x: "x";
|
|
91
92
|
web: "web";
|
|
92
93
|
web_and_x: "web_and_x";
|
|
93
|
-
x: "x";
|
|
94
94
|
}>>;
|
|
95
95
|
allowed_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
96
96
|
excluded_domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.15",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
"verify:insane-vendor": "bun scripts/verify-insane-vendor.ts"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@agentclientprotocol/sdk": "
|
|
52
|
+
"@agentclientprotocol/sdk": "1.2.1",
|
|
53
53
|
"@babel/parser": "^7.29.3",
|
|
54
54
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@sayknow-cli/stats": "0.3.
|
|
56
|
-
"@sayknow-cli/agent-core": "0.3.
|
|
57
|
-
"@sayknow-cli/ai": "0.3.
|
|
58
|
-
"@sayknow-cli/natives": "0.3.
|
|
59
|
-
"@sayknow-cli/tui": "0.3.
|
|
60
|
-
"@sayknow-cli/utils": "0.3.
|
|
55
|
+
"@sayknow-cli/stats": "0.3.15",
|
|
56
|
+
"@sayknow-cli/agent-core": "0.3.15",
|
|
57
|
+
"@sayknow-cli/ai": "0.3.15",
|
|
58
|
+
"@sayknow-cli/natives": "0.3.15",
|
|
59
|
+
"@sayknow-cli/tui": "0.3.15",
|
|
60
|
+
"@sayknow-cli/utils": "0.3.15",
|
|
61
61
|
"@puppeteer/browsers": "^2.13.0",
|
|
62
62
|
"@types/turndown": "5.0.6",
|
|
63
63
|
"@xterm/headless": "^6.0.0",
|
|
@@ -1,100 +1,260 @@
|
|
|
1
|
-
// G004 real-tmux smoke: exercises forceCloseSkcTmuxSession
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// G004 real-tmux smoke: exercises forceCloseSkcTmuxSession refusal boundaries
|
|
2
|
+
// against live tmux sessions. Generation-bound successful TERM/verdict/cleanup is
|
|
3
|
+
// covered by the issue evidence harness; this smoke proves incomplete and non-SKC
|
|
4
|
+
// live owners are never hard-killed.
|
|
5
5
|
import assert from "node:assert";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
6
10
|
|
|
7
11
|
import {
|
|
8
12
|
buildSkcTmuxExactOptionTarget,
|
|
13
|
+
buildSkcTmuxExactSessionTarget,
|
|
9
14
|
buildSkcTmuxProfileCommands,
|
|
10
15
|
resolveSkcTmuxCommand,
|
|
11
16
|
} from "../src/skc-runtime/tmux-common";
|
|
12
17
|
import { forceCloseSkcTmuxSession, removeSkcTmuxSession, statusSkcTmuxSession } from "../src/skc-runtime/tmux-sessions";
|
|
13
18
|
|
|
14
|
-
const
|
|
19
|
+
const runId = randomUUID().slice(0, 8);
|
|
20
|
+
const suffix = `${process.pid}-${runId}`;
|
|
21
|
+
const privateTmpdir = mkdtempSync(path.join(tmpdir(), "g4-"));
|
|
22
|
+
chmodSync(privateTmpdir, 0o700);
|
|
23
|
+
const socket = `g4-${runId}`;
|
|
24
|
+
const wrapper = path.join(privateTmpdir, "tmux-private.sh");
|
|
25
|
+
const scopeUnit = `skc-g004-${suffix}.scope`;
|
|
26
|
+
const tmuxBootstrapEnv: NodeJS.ProcessEnv = {
|
|
27
|
+
...process.env,
|
|
28
|
+
TMUX: "",
|
|
29
|
+
TMUX_PANE: "",
|
|
30
|
+
TMUX_TMPDIR: privateTmpdir,
|
|
31
|
+
};
|
|
32
|
+
const tmuxBinary = resolveSkcTmuxCommand(tmuxBootstrapEnv);
|
|
33
|
+
const privateEnv: NodeJS.ProcessEnv = {
|
|
34
|
+
...tmuxBootstrapEnv,
|
|
35
|
+
SKC_TMUX_COMMAND: wrapper,
|
|
36
|
+
};
|
|
37
|
+
let scopeRunner: Bun.Subprocess<"ignore", "ignore", "pipe"> | null = null;
|
|
15
38
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
39
|
+
function shellQuote(value: string): string {
|
|
40
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function bounded(value: string): string {
|
|
44
|
+
return value.trim().slice(0, 512) || "no diagnostic";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sh(args: string[]): { code: number; out: string; err: string } {
|
|
48
|
+
try {
|
|
49
|
+
const r = Bun.spawnSync([wrapper, ...args], { env: privateEnv, stdout: "pipe", stderr: "pipe" });
|
|
50
|
+
return { code: r.exitCode, out: r.stdout.toString().trim(), err: r.stderr.toString().trim() };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return { code: -1, out: "", err: bounded(String(error)) };
|
|
53
|
+
}
|
|
19
54
|
}
|
|
20
55
|
|
|
21
56
|
function makeRawSession(name: string): void {
|
|
22
57
|
const r = sh(["new-session", "-d", "-s", name, "sleep 600"]);
|
|
23
|
-
if (r.code !== 0) throw new Error(`failed to create tmux session ${name}: ${r.err}`);
|
|
58
|
+
if (r.code !== 0) throw new Error(`failed to create private tmux session ${name}: ${bounded(r.err)}`);
|
|
24
59
|
}
|
|
25
60
|
|
|
26
61
|
function tagAsSkc(name: string, sessionId?: string): void {
|
|
27
|
-
const target = buildSkcTmuxExactOptionTarget(name);
|
|
28
|
-
for (const cmd of buildSkcTmuxProfileCommands(target,
|
|
62
|
+
const target = buildSkcTmuxExactOptionTarget(name, { env: privateEnv });
|
|
63
|
+
for (const cmd of buildSkcTmuxProfileCommands(target, privateEnv, { sessionId })) {
|
|
29
64
|
const r = sh(cmd.args);
|
|
30
|
-
if (r.code !== 0) throw new Error(`failed to tag ${name} (${cmd.description}): ${r.err}`);
|
|
65
|
+
if (r.code !== 0) throw new Error(`failed to tag ${name} (${cmd.description}): ${bounded(r.err)}`);
|
|
31
66
|
}
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
function exists(name: string): boolean {
|
|
35
|
-
return sh(["has-session", "-t",
|
|
70
|
+
return sh(["has-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]).code === 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isPrivateSessionAbsent(name: string): boolean {
|
|
74
|
+
const result = sh(["has-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]);
|
|
75
|
+
return result.code !== 0 && /(?:no server running|can't find session|no sessions)/i.test(result.err);
|
|
36
76
|
}
|
|
37
77
|
|
|
38
|
-
function
|
|
39
|
-
sh([
|
|
78
|
+
function privateServerPid(session: string): number | null {
|
|
79
|
+
const result = sh([
|
|
80
|
+
"display-message",
|
|
81
|
+
"-p",
|
|
82
|
+
"-t",
|
|
83
|
+
buildSkcTmuxExactOptionTarget(session, { env: privateEnv }),
|
|
84
|
+
"#{pid}",
|
|
85
|
+
]);
|
|
86
|
+
const pid = Number.parseInt(result.out, 10);
|
|
87
|
+
return result.code === 0 && Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function scopeIsActive(): boolean {
|
|
91
|
+
return (
|
|
92
|
+
Bun.spawnSync(["systemctl", "--user", "is-active", "--quiet", scopeUnit], { stdout: "ignore", stderr: "ignore" })
|
|
93
|
+
.exitCode === 0
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function serverIsProvenInScope(session: string): boolean {
|
|
98
|
+
const pid = privateServerPid(session);
|
|
99
|
+
if (pid == null) return false;
|
|
100
|
+
const controlGroup = Bun.spawnSync(
|
|
101
|
+
["systemctl", "--user", "show", scopeUnit, "--property=ControlGroup", "--value"],
|
|
102
|
+
{ stdout: "pipe", stderr: "ignore" },
|
|
103
|
+
);
|
|
104
|
+
const group = controlGroup.stdout.toString().trim();
|
|
105
|
+
if (controlGroup.exitCode !== 0 || !group.startsWith("/")) return false;
|
|
106
|
+
try {
|
|
107
|
+
return readFileSync(`/proc/${pid}/cgroup`, "utf8")
|
|
108
|
+
.split("\n")
|
|
109
|
+
.some(line => line.endsWith(group));
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function makeFirstPrivateSession(name: string): Promise<void> {
|
|
116
|
+
if (process.platform !== "linux") {
|
|
117
|
+
makeRawSession(name);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
scopeRunner = Bun.spawn(
|
|
122
|
+
[
|
|
123
|
+
"systemd-run",
|
|
124
|
+
"--user",
|
|
125
|
+
"--quiet",
|
|
126
|
+
"--collect",
|
|
127
|
+
`--setenv=TMUX_TMPDIR=${privateTmpdir}`,
|
|
128
|
+
"--scope",
|
|
129
|
+
`--unit=${scopeUnit}`,
|
|
130
|
+
wrapper,
|
|
131
|
+
"new-session",
|
|
132
|
+
"-d",
|
|
133
|
+
"-s",
|
|
134
|
+
name,
|
|
135
|
+
"sleep 600",
|
|
136
|
+
],
|
|
137
|
+
{ env: privateEnv, stdout: "ignore", stderr: "pipe" },
|
|
138
|
+
);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new Error(`failed to provision private user scope ${scopeUnit}: ${bounded(String(error))}`);
|
|
141
|
+
}
|
|
142
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
143
|
+
if (scopeIsActive() && exists(name) && serverIsProvenInScope(name)) return;
|
|
144
|
+
await Bun.sleep(50);
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`failed to provision a proven private user scope ${scopeUnit}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function cleanupOwnedResources(names: string[]): Promise<void> {
|
|
150
|
+
const failures: string[] = [];
|
|
151
|
+
for (const name of names) {
|
|
152
|
+
const result = sh(["kill-session", "-t", buildSkcTmuxExactSessionTarget(name, { env: privateEnv })]);
|
|
153
|
+
if (result.code !== 0 && !isPrivateSessionAbsent(name)) failures.push(`session ${name}: ${bounded(result.err)}`);
|
|
154
|
+
}
|
|
155
|
+
const server = sh(["kill-server"]);
|
|
156
|
+
if (server.code !== 0) {
|
|
157
|
+
const probe = sh(["list-sessions"]);
|
|
158
|
+
if (!/(?:no server running|failed to connect to server|error connecting to)/i.test(probe.err))
|
|
159
|
+
failures.push(`server: ${bounded(server.err)}`);
|
|
160
|
+
}
|
|
161
|
+
if (process.platform === "linux") {
|
|
162
|
+
const stop = Bun.spawnSync(["systemctl", "--user", "stop", scopeUnit], { stdout: "pipe", stderr: "pipe" });
|
|
163
|
+
const state = Bun.spawnSync(
|
|
164
|
+
["systemctl", "--user", "show", scopeUnit, "--property=ActiveState", "--property=LoadState", "--value"],
|
|
165
|
+
{ stdout: "pipe", stderr: "pipe" },
|
|
166
|
+
);
|
|
167
|
+
const states = state.stdout.toString().trim().split("\n");
|
|
168
|
+
const scopeAbsent = state.exitCode === 0 && (states.includes("inactive") || states.includes("not-found"));
|
|
169
|
+
if (stop.exitCode !== 0 && !scopeAbsent) failures.push(`scope stop: ${bounded(stop.stderr.toString())}`);
|
|
170
|
+
if (!scopeAbsent)
|
|
171
|
+
failures.push(`scope remains present: ${bounded(state.stderr.toString() || state.stdout.toString())}`);
|
|
172
|
+
if (scopeRunner != null) {
|
|
173
|
+
const exited = await Promise.race([scopeRunner.exited.then(() => true), Bun.sleep(3_000).then(() => false)]);
|
|
174
|
+
if (!exited) {
|
|
175
|
+
try {
|
|
176
|
+
scopeRunner.kill();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
failures.push(`scope runner: ${bounded(String(error))}`);
|
|
179
|
+
}
|
|
180
|
+
const killed = await Promise.race([
|
|
181
|
+
scopeRunner.exited.then(() => true),
|
|
182
|
+
Bun.sleep(1_000).then(() => false),
|
|
183
|
+
]);
|
|
184
|
+
if (!killed) failures.push("scope runner did not exit after scope cleanup");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
rmSync(privateTmpdir, { recursive: true });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (existsSync(privateTmpdir)) failures.push(`temporary directory: ${bounded(String(error))}`);
|
|
192
|
+
}
|
|
193
|
+
if (existsSync(privateTmpdir)) failures.push("temporary directory remains present");
|
|
194
|
+
if (failures.length > 0) throw new Error(`g004 cleanup incomplete: ${failures.join("; ")}`);
|
|
40
195
|
}
|
|
41
196
|
|
|
42
|
-
const suffix = `${process.pid}-${Date.now()}`;
|
|
43
197
|
const live = `skc_g004live_${suffix}`;
|
|
44
198
|
const raw = `g004raw_${suffix}`;
|
|
45
199
|
const mism = `skc_g004mism_${suffix}`;
|
|
46
|
-
const cleanup
|
|
200
|
+
const cleanup = [live, raw, mism];
|
|
47
201
|
|
|
48
202
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
203
|
+
writeFileSync(wrapper, `#!/bin/sh\nexec ${shellQuote(tmuxBinary)} -L ${shellQuote(socket)} "$@"\n`, { mode: 0o700 });
|
|
204
|
+
chmodSync(wrapper, 0o700);
|
|
205
|
+
|
|
206
|
+
// 1. Incompletely tagged LIVE session: remove refuses and force-close fails closed.
|
|
207
|
+
await makeFirstPrivateSession(live);
|
|
51
208
|
tagAsSkc(live, "sess-g004");
|
|
52
|
-
const status = statusSkcTmuxSession(live);
|
|
209
|
+
const status = statusSkcTmuxSession(live, privateEnv);
|
|
53
210
|
assert.equal(status.profile, "1", "session must be recognized as SKC-managed");
|
|
54
211
|
assert.ok(status.panePids.length > 0, "session must have a live pane (sleep)");
|
|
55
|
-
|
|
212
|
+
process.stdout.write(`[g004] incomplete SKC session up: ${live} panePids=${status.panePids.length}\n`);
|
|
56
213
|
|
|
57
214
|
let removeRefused = false;
|
|
58
215
|
try {
|
|
59
|
-
removeSkcTmuxSession(live);
|
|
216
|
+
removeSkcTmuxSession(live, privateEnv);
|
|
60
217
|
} catch (e) {
|
|
61
218
|
removeRefused = /skc_tmux_session_live/.test(String(e));
|
|
62
219
|
}
|
|
63
220
|
assert.ok(removeRefused, "removeSkcTmuxSession must REFUSE a live pane");
|
|
64
|
-
console.log("[g004] remove refused live session (expected)");
|
|
65
221
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
222
|
+
let ownerUnverifiable = false;
|
|
223
|
+
try {
|
|
224
|
+
await forceCloseSkcTmuxSession(live, privateEnv, "sess-g004");
|
|
225
|
+
} catch (e) {
|
|
226
|
+
ownerUnverifiable = /skc_tmux_owner_unverifiable/.test(String(e));
|
|
227
|
+
}
|
|
228
|
+
assert.ok(ownerUnverifiable, "force-close must refuse incomplete owner provenance");
|
|
229
|
+
assert.ok(exists(live), "incompletely tagged session must be left untouched");
|
|
230
|
+
process.stdout.write("[g004] force-close refused incomplete owner provenance (expected)\n");
|
|
70
231
|
|
|
71
232
|
// 2. Non-SKC (untagged) session: force-close must refuse.
|
|
72
233
|
makeRawSession(raw);
|
|
73
234
|
let notManaged = false;
|
|
74
235
|
try {
|
|
75
|
-
forceCloseSkcTmuxSession(raw,
|
|
236
|
+
await forceCloseSkcTmuxSession(raw, privateEnv);
|
|
76
237
|
} catch (e) {
|
|
77
|
-
notManaged = /skc_tmux_session_(not_managed|not_found)/.test(String(e));
|
|
238
|
+
notManaged = /skc_tmux_session_(not_managed|not_found|untagged)/.test(String(e));
|
|
78
239
|
}
|
|
79
240
|
assert.ok(notManaged, "force-close must refuse a non-SKC tmux session");
|
|
80
241
|
assert.ok(exists(raw), "non-SKC session must be left untouched");
|
|
81
|
-
|
|
82
|
-
console.log("[g004] force-close refused + preserved non-SKC session (expected)");
|
|
242
|
+
process.stdout.write("[g004] force-close refused + preserved non-SKC session (expected)\n");
|
|
83
243
|
|
|
84
244
|
// 3. SKC session with a MISMATCHED expected session id: must refuse.
|
|
85
245
|
makeRawSession(mism);
|
|
86
246
|
tagAsSkc(mism, "sess-real");
|
|
87
247
|
let idMismatch = false;
|
|
88
248
|
try {
|
|
89
|
-
forceCloseSkcTmuxSession(mism,
|
|
249
|
+
await forceCloseSkcTmuxSession(mism, privateEnv, "sess-WRONG");
|
|
90
250
|
} catch (e) {
|
|
91
251
|
idMismatch = /skc_tmux_session_id_mismatch/.test(String(e));
|
|
92
252
|
}
|
|
93
253
|
assert.ok(idMismatch, "force-close must refuse on session-id mismatch");
|
|
94
254
|
assert.ok(exists(mism), "mismatched session must be left untouched");
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log("[g004] PASS: forceCloseSkcTmuxSession verified against live tmux");
|
|
255
|
+
process.stdout.write("[g004] force-close refused on session-id mismatch (expected)\n");
|
|
98
256
|
} finally {
|
|
99
|
-
|
|
257
|
+
await cleanupOwnedResources(cleanup);
|
|
100
258
|
}
|
|
259
|
+
|
|
260
|
+
process.stdout.write("[g004] PASS: forceCloseSkcTmuxSession refusal boundaries verified against live tmux\n");
|
package/src/cli/daemon-cli.ts
CHANGED
|
@@ -12,8 +12,14 @@ import type {
|
|
|
12
12
|
DaemonKind,
|
|
13
13
|
DaemonOperationOptions,
|
|
14
14
|
DaemonOperationResult,
|
|
15
|
-
DaemonStatus,
|
|
16
15
|
} from "../daemon/control-types";
|
|
16
|
+
import {
|
|
17
|
+
DAEMON_ACTION_TOKENS,
|
|
18
|
+
DAEMON_EXIT,
|
|
19
|
+
formatDaemonResult,
|
|
20
|
+
formatDaemonStatus,
|
|
21
|
+
resolveDaemonAction,
|
|
22
|
+
} from "../daemon/operator-contract";
|
|
17
23
|
|
|
18
24
|
export type DaemonCliAction = "list" | "status" | "stop" | "reload";
|
|
19
25
|
|
|
@@ -26,6 +32,8 @@ export interface DaemonCommandArgs {
|
|
|
26
32
|
gracefulTimeoutMs?: number;
|
|
27
33
|
killTimeoutMs?: number;
|
|
28
34
|
spawnIfStopped?: boolean;
|
|
35
|
+
/** Show runtime detail and the full roots list in human output. */
|
|
36
|
+
verbose?: boolean;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export interface DaemonCommandDeps {
|
|
@@ -33,18 +41,20 @@ export interface DaemonCommandDeps {
|
|
|
33
41
|
controllers?: BuiltInDaemonController[];
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
const KNOWN_ACTIONS: DaemonCliAction[] = ["list", "status", "stop", "reload"];
|
|
37
44
|
const KNOWN_KINDS: DaemonKind[] = ["telegram"];
|
|
38
45
|
|
|
39
46
|
export function parseDaemonArgs(argv: string[]): DaemonCommandArgs | undefined {
|
|
40
47
|
if (argv.length === 0 || argv[0] !== "daemon") return undefined;
|
|
41
48
|
const rest = argv.slice(1);
|
|
42
|
-
const
|
|
43
|
-
const
|
|
49
|
+
const resolved = resolveDaemonAction(rest[0]);
|
|
50
|
+
const isActionToken = (DAEMON_ACTION_TOKENS as readonly string[]).includes(rest[0] ?? "");
|
|
51
|
+
const action = (resolved ?? "status") as DaemonCliAction;
|
|
52
|
+
const positional = isActionToken ? rest.slice(1) : rest;
|
|
44
53
|
const kinds: DaemonKind[] = [];
|
|
45
54
|
let all = false;
|
|
46
55
|
let json = false;
|
|
47
56
|
let force = false;
|
|
57
|
+
let verbose = false;
|
|
48
58
|
let gracefulTimeoutMs: number | undefined;
|
|
49
59
|
let killTimeoutMs: number | undefined;
|
|
50
60
|
let spawnIfStopped: boolean | undefined;
|
|
@@ -53,34 +63,14 @@ export function parseDaemonArgs(argv: string[]): DaemonCommandArgs | undefined {
|
|
|
53
63
|
if (arg === "--all") all = true;
|
|
54
64
|
else if (arg === "--json") json = true;
|
|
55
65
|
else if (arg === "--force") force = true;
|
|
66
|
+
else if (arg === "--verbose" || arg === "-v") verbose = true;
|
|
56
67
|
else if (arg === "--spawn-if-stopped") spawnIfStopped = true;
|
|
57
68
|
else if (arg === "--graceful-timeout-ms") gracefulTimeoutMs = Number.parseInt(positional[++i], 10);
|
|
58
69
|
else if (arg === "--kill-timeout-ms") killTimeoutMs = Number.parseInt(positional[++i], 10);
|
|
59
70
|
else if (!arg.startsWith("--") && (KNOWN_KINDS as string[]).includes(arg)) kinds.push(arg as DaemonKind);
|
|
60
71
|
}
|
|
61
|
-
return { action, kinds, all, json, force, gracefulTimeoutMs, killTimeoutMs, spawnIfStopped };
|
|
72
|
+
return { action, kinds, all, json, force, verbose, gracefulTimeoutMs, killTimeoutMs, spawnIfStopped };
|
|
62
73
|
}
|
|
63
|
-
|
|
64
|
-
function formatStatus(status: DaemonStatus): string {
|
|
65
|
-
const parts = [
|
|
66
|
-
`${status.kind}: ${status.health}`,
|
|
67
|
-
status.configured ? undefined : "(not configured)",
|
|
68
|
-
status.pid !== undefined ? `pid=${status.pid}` : undefined,
|
|
69
|
-
status.ownerId ? `owner=${status.ownerId}` : undefined,
|
|
70
|
-
status.rootCount !== undefined ? `roots=${status.rootCount}` : undefined,
|
|
71
|
-
`mode=${status.runtime.mode}`,
|
|
72
|
-
].filter(Boolean);
|
|
73
|
-
let line = parts.join(" ");
|
|
74
|
-
if (status.runtime.warning) line += `\n warning: ${status.runtime.warning}`;
|
|
75
|
-
return line;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function formatResult(result: DaemonOperationResult): string {
|
|
79
|
-
const head = `${result.kind} ${result.action}: ${result.ok ? "ok" : "failed"} — ${result.message}`;
|
|
80
|
-
const warnings = result.warnings.map(w => `\n warning: ${w}`).join("");
|
|
81
|
-
return head + warnings;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
74
|
export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonCommandDeps = {}): Promise<void> {
|
|
85
75
|
const unknownKinds = cmd.kinds.filter(kind => !(KNOWN_KINDS as string[]).includes(kind));
|
|
86
76
|
if (unknownKinds.length > 0) {
|
|
@@ -98,7 +88,7 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
|
|
|
98
88
|
if (cmd.json) {
|
|
99
89
|
process.stdout.write(`${JSON.stringify(statuses, null, 2)}\n`);
|
|
100
90
|
} else {
|
|
101
|
-
process.stdout.write(`${statuses.map(
|
|
91
|
+
process.stdout.write(`${statuses.map(s => formatDaemonStatus(s, { verbose: cmd.verbose })).join("\n")}\n`);
|
|
102
92
|
}
|
|
103
93
|
return;
|
|
104
94
|
}
|
|
@@ -116,7 +106,7 @@ export async function runDaemonCommand(cmd: DaemonCommandArgs, deps: DaemonComma
|
|
|
116
106
|
if (cmd.json) {
|
|
117
107
|
process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
|
|
118
108
|
} else {
|
|
119
|
-
process.stdout.write(`${results.map(
|
|
109
|
+
process.stdout.write(`${results.map(formatDaemonResult).join("\n")}\n`);
|
|
120
110
|
}
|
|
121
|
-
if (results.some(r => !r.ok)) process.exitCode =
|
|
111
|
+
if (results.some(r => !r.ok)) process.exitCode = DAEMON_EXIT.failure;
|
|
122
112
|
}
|