@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
buildMallocReexecArgv,
|
|
4
|
+
formatMallocEnvNotice,
|
|
5
|
+
hasMacOSMallocEnv,
|
|
6
|
+
MACOS_MALLOC_ENV_VARS,
|
|
7
|
+
MALLOC_ENV_REEXEC_GUARD,
|
|
8
|
+
shouldReexecForMacOSMallocEnv,
|
|
9
|
+
} from "./malloc-env-guard";
|
|
10
|
+
|
|
11
|
+
describe("hasMacOSMallocEnv", () => {
|
|
12
|
+
it("detects MallocStackLogging", () => {
|
|
13
|
+
expect(hasMacOSMallocEnv({ MallocStackLogging: "0" })).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("detects MallocStackLoggingNoCompact", () => {
|
|
17
|
+
expect(hasMacOSMallocEnv({ MallocStackLoggingNoCompact: "1" })).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("is false when neither var is present", () => {
|
|
21
|
+
expect(hasMacOSMallocEnv({ PATH: "/usr/bin", HOME: "/home/x" })).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("is false for an empty environment", () => {
|
|
25
|
+
expect(hasMacOSMallocEnv({})).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("shouldReexecForMacOSMallocEnv", () => {
|
|
30
|
+
it("is true on darwin with a malloc var and no loop guard", () => {
|
|
31
|
+
expect(shouldReexecForMacOSMallocEnv({ MallocStackLogging: "0" }, "darwin")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("is false off darwin even when a malloc var is present", () => {
|
|
35
|
+
expect(shouldReexecForMacOSMallocEnv({ MallocStackLogging: "0" }, "linux")).toBe(false);
|
|
36
|
+
expect(shouldReexecForMacOSMallocEnv({ MallocStackLogging: "0" }, "win32")).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("is false when the loop guard is already set (already re-exec'd)", () => {
|
|
40
|
+
expect(shouldReexecForMacOSMallocEnv({ MallocStackLogging: "0", [MALLOC_ENV_REEXEC_GUARD]: "1" }, "darwin")).toBe(
|
|
41
|
+
false,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("is false on darwin when no malloc var is present", () => {
|
|
46
|
+
expect(shouldReexecForMacOSMallocEnv({ PATH: "/usr/bin" }, "darwin")).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("buildMallocReexecArgv", () => {
|
|
51
|
+
it("drops the embedded virtual entry for a POSIX compiled binary", () => {
|
|
52
|
+
expect(buildMallocReexecArgv(["/exe", "/$bunfs/root/cli", "run", "--flag"], "/exe")).toEqual([
|
|
53
|
+
"/exe",
|
|
54
|
+
"run",
|
|
55
|
+
"--flag",
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("drops the embedded virtual entry for a Windows compiled binary", () => {
|
|
60
|
+
expect(buildMallocReexecArgv(["C:/exe.exe", "B:\\~BUN\\root\\cli", "run"], "C:/exe.exe")).toEqual([
|
|
61
|
+
"C:/exe.exe",
|
|
62
|
+
"run",
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("drops the percent-encoded embedded virtual entry", () => {
|
|
67
|
+
expect(buildMallocReexecArgv(["/exe", "B:\\%7EBUN\\root\\cli", "run"], "/exe")).toEqual(["/exe", "run"]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("keeps the on-disk script for a source/dev-link run", () => {
|
|
71
|
+
expect(
|
|
72
|
+
buildMallocReexecArgv(["/usr/bin/bun", "/repo/packages/coding-agent/src/cli.ts", "run", "-x"], "/usr/bin/bun"),
|
|
73
|
+
).toEqual(["/usr/bin/bun", "/repo/packages/coding-agent/src/cli.ts", "run", "-x"]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("handles a source run with no user args", () => {
|
|
77
|
+
expect(buildMallocReexecArgv(["/usr/bin/bun", "/repo/cli.ts"], "/usr/bin/bun")).toEqual([
|
|
78
|
+
"/usr/bin/bun",
|
|
79
|
+
"/repo/cli.ts",
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("formatMallocEnvNotice", () => {
|
|
85
|
+
it("names both env vars and the permanent launchctl fix", () => {
|
|
86
|
+
const notice = formatMallocEnvNotice();
|
|
87
|
+
for (const name of MACOS_MALLOC_ENV_VARS) {
|
|
88
|
+
expect(notice).toContain(name);
|
|
89
|
+
}
|
|
90
|
+
expect(notice).toContain("launchctl unsetenv MallocStackLogging");
|
|
91
|
+
expect(notice).toContain("launchctl unsetenv MallocStackLoggingNoCompact");
|
|
92
|
+
expect(notice.endsWith("\n")).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("module constants", () => {
|
|
97
|
+
it("exposes exactly the two macOS malloc env vars", () => {
|
|
98
|
+
expect([...MACOS_MALLOC_ENV_VARS]).toEqual(["MallocStackLogging", "MallocStackLoggingNoCompact"]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("uses a namespaced loop-guard variable", () => {
|
|
102
|
+
expect(MALLOC_ENV_REEXEC_GUARD).toBe("SKC_MALLOC_ENV_REEXEC");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS malloc-stack-logging launch boundary.
|
|
3
|
+
*
|
|
4
|
+
* On macOS, an inherited `MallocStackLogging` / `MallocStackLoggingNoCompact`
|
|
5
|
+
* environment variable (from an Xcode scheme's malloc diagnostics, Instruments,
|
|
6
|
+
* `launchctl setenv`, or a debug-attached shell) makes libmalloc print
|
|
7
|
+
*
|
|
8
|
+
* MallocStackLogging: can't turn off malloc stack logging because it was not enabled.
|
|
9
|
+
*
|
|
10
|
+
* to the stderr of every TTY-attached process. Bun snapshots the spawn-default
|
|
11
|
+
* environment at process startup — before any JS runs — so neither
|
|
12
|
+
* `delete process.env.X` nor a real libc `unsetenv()` cleans the environment
|
|
13
|
+
* that children inherit by default (verified on Bun 1.3.14). Inside the TUI,
|
|
14
|
+
* every PTY-spawned child then repeats the warning straight into the rendered
|
|
15
|
+
* frame and corrupts the display.
|
|
16
|
+
*
|
|
17
|
+
* The only way to hand children a clean startup snapshot is to start the primary
|
|
18
|
+
* process with a clean environment. This module re-execs the exact invocation
|
|
19
|
+
* once (darwin-only) with a `filterProcessEnv`-scrubbed environment and a
|
|
20
|
+
* `SKC_MALLOC_ENV_REEXEC` loop guard, before any fast path or subprocess spawn.
|
|
21
|
+
* The re-exec'd process — and therefore every downstream lane: `Bun.spawn`
|
|
22
|
+
* defaults, `node:child_process`, the native PTY, the tmux owner, plugin
|
|
23
|
+
* installs, and subagents — starts from a clean snapshot.
|
|
24
|
+
*
|
|
25
|
+
* The initial contaminated process's own libmalloc line is emitted by libc
|
|
26
|
+
* before any JS runs and cannot be suppressed from inside the process; it
|
|
27
|
+
* appears at most once per launch. A one-time actionable notice is written to
|
|
28
|
+
* interactive stderr (outside the rendered frame) pointing at the permanent
|
|
29
|
+
* `launchctl unsetenv` fix.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { filterProcessEnv } from "@sayknow-cli/utils/env";
|
|
33
|
+
import type { Subprocess } from "bun";
|
|
34
|
+
|
|
35
|
+
/** Env vars that make macOS libmalloc write to a TTY when inherited. */
|
|
36
|
+
export const MACOS_MALLOC_ENV_VARS = ["MallocStackLogging", "MallocStackLoggingNoCompact"] as const;
|
|
37
|
+
|
|
38
|
+
/** Loop guard set on the scrubbed re-exec so it never re-execs itself again. */
|
|
39
|
+
export const MALLOC_ENV_REEXEC_GUARD = "SKC_MALLOC_ENV_REEXEC";
|
|
40
|
+
|
|
41
|
+
/** True when at least one macOS malloc-stack-logging var is present in `env`. */
|
|
42
|
+
export function hasMacOSMallocEnv(env: Record<string, string | undefined>): boolean {
|
|
43
|
+
return MACOS_MALLOC_ENV_VARS.some(name => env[name] !== undefined);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True iff we must re-exec to escape a contaminated macOS startup snapshot:
|
|
48
|
+
* darwin, not already inside the scrubbed re-exec, and at least one malloc var
|
|
49
|
+
* present. Off darwin or on the re-exec'd child this is always false, so the
|
|
50
|
+
* common launch path pays only two cheap lookups.
|
|
51
|
+
*/
|
|
52
|
+
export function shouldReexecForMacOSMallocEnv(
|
|
53
|
+
env: Record<string, string | undefined>,
|
|
54
|
+
platform: NodeJS.Platform,
|
|
55
|
+
): boolean {
|
|
56
|
+
return platform === "darwin" && env[MALLOC_ENV_REEXEC_GUARD] === undefined && hasMacOSMallocEnv(env);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reconstruct the argv that re-runs the exact same program image.
|
|
61
|
+
*
|
|
62
|
+
* - Compiled single-file executable: argv[1] is the embedded virtual entry
|
|
63
|
+
* (`/$bunfs/…` on POSIX, `B:\~BUN\…` on Windows), which must be dropped — the
|
|
64
|
+
* executable re-embeds its own entry. Re-run `[exe, ...userArgs]`.
|
|
65
|
+
* - Source / dev-link run: argv[1] is the on-disk script that must be passed
|
|
66
|
+
* back to the interpreter. Re-run `[interpreter, script, ...userArgs]`.
|
|
67
|
+
*/
|
|
68
|
+
export function buildMallocReexecArgv(argv: readonly string[], execPath: string): string[] {
|
|
69
|
+
const entry = argv[1];
|
|
70
|
+
const isCompiledEntry =
|
|
71
|
+
typeof entry === "string" && (entry.includes("/$bunfs/") || entry.includes("~BUN") || entry.includes("%7EBUN"));
|
|
72
|
+
if (isCompiledEntry) {
|
|
73
|
+
return [execPath, ...argv.slice(2)];
|
|
74
|
+
}
|
|
75
|
+
return [execPath, ...argv.slice(1)];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One-time, human-actionable notice pointing at the permanent environment fix. */
|
|
79
|
+
export function formatMallocEnvNotice(): string {
|
|
80
|
+
return (
|
|
81
|
+
"sayknow-cli: detected macOS malloc-stack-logging environment variables " +
|
|
82
|
+
"(MallocStackLogging / MallocStackLoggingNoCompact); relaunching once with a " +
|
|
83
|
+
"scrubbed environment so they do not flood the terminal.\n" +
|
|
84
|
+
" Remove them permanently with:\n" +
|
|
85
|
+
" unset MallocStackLogging MallocStackLoggingNoCompact\n" +
|
|
86
|
+
" launchctl unsetenv MallocStackLogging\n" +
|
|
87
|
+
" launchctl unsetenv MallocStackLoggingNoCompact\n"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Re-exec the current process once with a scrubbed environment and propagate the
|
|
93
|
+
* child's exit code. Callers MUST gate this behind `shouldReexecForMacOSMallocEnv`.
|
|
94
|
+
*
|
|
95
|
+
* Returns the child exit code, or `null` if the re-exec could not be spawned (in
|
|
96
|
+
* which case the caller falls back to running in the current, contaminated
|
|
97
|
+
* process — degraded but functional; the native PTY boundary and
|
|
98
|
+
* `filterProcessEnv` still keep managed children clean).
|
|
99
|
+
*/
|
|
100
|
+
export async function reexecWithScrubbedMallocEnv(): Promise<number | null> {
|
|
101
|
+
const cleanEnv = filterProcessEnv(process.env);
|
|
102
|
+
cleanEnv[MALLOC_ENV_REEXEC_GUARD] = "1";
|
|
103
|
+
const childArgv = buildMallocReexecArgv(process.argv, process.execPath);
|
|
104
|
+
|
|
105
|
+
// Surface the diagnostic exactly once, before the TUI enters its alternate
|
|
106
|
+
// screen, and only on an interactive terminal (piped/CI stderr is not a TTY,
|
|
107
|
+
// so libmalloc stays silent there and the notice would just be noise).
|
|
108
|
+
if (process.stderr.isTTY) {
|
|
109
|
+
process.stderr.write(formatMallocEnvNotice());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let child: Subprocess;
|
|
113
|
+
try {
|
|
114
|
+
child = Bun.spawn(childArgv, {
|
|
115
|
+
env: cleanEnv,
|
|
116
|
+
stdin: "inherit",
|
|
117
|
+
stdout: "inherit",
|
|
118
|
+
stderr: "inherit",
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Keep the wrapper alive on SIGINT/SIGTERM so it reaps the child and
|
|
125
|
+
// propagates its exit code instead of dying first, and forward the signal so
|
|
126
|
+
// the scrubbed child terminates too. Interactive Ctrl-C is delivered to the
|
|
127
|
+
// whole foreground process group (and the TUI's raw mode turns it into a 0x03
|
|
128
|
+
// byte, not a signal, anyway), so re-forwarding is a harmless no-op once the
|
|
129
|
+
// child is already handling or exiting. Targeted delivery — `kill -INT <pid>`
|
|
130
|
+
// or a supervisor's `child.kill("SIGINT")` — reaches only the wrapper, so
|
|
131
|
+
// without forwarding the launch would be uncancellable outside a terminal.
|
|
132
|
+
const forwardSignal = (signal: NodeJS.Signals) => () => {
|
|
133
|
+
try {
|
|
134
|
+
child.kill(signal);
|
|
135
|
+
} catch {
|
|
136
|
+
// Child already gone; nothing to forward.
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const onSigint = forwardSignal("SIGINT");
|
|
140
|
+
const onSigterm = forwardSignal("SIGTERM");
|
|
141
|
+
process.on("SIGINT", onSigint);
|
|
142
|
+
process.on("SIGTERM", onSigterm);
|
|
143
|
+
try {
|
|
144
|
+
return await child.exited;
|
|
145
|
+
} finally {
|
|
146
|
+
process.off("SIGINT", onSigint);
|
|
147
|
+
process.off("SIGTERM", onSigterm);
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -7,9 +7,19 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { APP_NAME } from "@sayknow-cli/utils/dirs";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import type { Settings } from "../config/settings";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import { maskToken } from "../notifications/config";
|
|
11
|
+
import {
|
|
12
|
+
buildNotificationStatusReport,
|
|
13
|
+
checkNotificationHealth,
|
|
14
|
+
formatNotificationHealthReport,
|
|
15
|
+
formatNotificationRecoveryReport,
|
|
16
|
+
formatNotificationStatusReport,
|
|
17
|
+
formatNotificationTestResult,
|
|
18
|
+
recoverNotifications,
|
|
19
|
+
sendNotificationTest,
|
|
20
|
+
} from "../notifications/notification-service";
|
|
21
|
+
|
|
22
|
+
export type NotifyAction = "setup" | "status" | "health" | "test" | "recovery" | "daemon-internal";
|
|
13
23
|
|
|
14
24
|
export interface NotifyCommandArgs {
|
|
15
25
|
action: NotifyAction;
|
|
@@ -18,6 +28,8 @@ export interface NotifyCommandArgs {
|
|
|
18
28
|
token?: string;
|
|
19
29
|
chatId?: string;
|
|
20
30
|
redact?: boolean;
|
|
31
|
+
probe?: boolean;
|
|
32
|
+
message?: string;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export interface NotifyCommandDeps {
|
|
@@ -93,6 +105,19 @@ export function parseNotifyArgs(args: string[]): NotifyCommandArgs | undefined {
|
|
|
93
105
|
redact: rest.includes("--redact"),
|
|
94
106
|
};
|
|
95
107
|
}
|
|
108
|
+
if (action === "health" || action === "test" || action === "recovery") {
|
|
109
|
+
const rest = args.slice(2);
|
|
110
|
+
const flag = (name: string): string | undefined => {
|
|
111
|
+
const i = rest.indexOf(name);
|
|
112
|
+
return i >= 0 ? rest[i + 1] : undefined;
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
action,
|
|
116
|
+
rawArgs: rest,
|
|
117
|
+
probe: rest.includes("--probe"),
|
|
118
|
+
message: flag("--message"),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
96
121
|
if (action === "daemon-internal") {
|
|
97
122
|
return {
|
|
98
123
|
action,
|
|
@@ -117,6 +142,15 @@ export async function runNotifyCommand(cmd: NotifyCommandArgs, deps: NotifyComma
|
|
|
117
142
|
case "status":
|
|
118
143
|
await runStatus(deps);
|
|
119
144
|
return;
|
|
145
|
+
case "health":
|
|
146
|
+
await runHealth(deps, cmd);
|
|
147
|
+
return;
|
|
148
|
+
case "test":
|
|
149
|
+
await runTest(deps, cmd);
|
|
150
|
+
return;
|
|
151
|
+
case "recovery":
|
|
152
|
+
await runRecovery(deps);
|
|
153
|
+
return;
|
|
120
154
|
case "daemon-internal": {
|
|
121
155
|
const m = await import("../notifications/telegram-daemon-cli");
|
|
122
156
|
if (cmd.smoke) {
|
|
@@ -411,20 +445,42 @@ async function verifyThreadedMode(
|
|
|
411
445
|
|
|
412
446
|
async function runStatus(deps: NotifyCommandDeps): Promise<void> {
|
|
413
447
|
const settings = await getSettings(deps);
|
|
414
|
-
const
|
|
448
|
+
const report = buildNotificationStatusReport(settings);
|
|
415
449
|
process.stdout.write(
|
|
416
|
-
`${chalk.bold("Notifications")}\n
|
|
417
|
-
` enabled: ${cfg.enabled}\n` +
|
|
418
|
-
` telegram.botToken: ${maskToken(cfg.botToken)}\n` +
|
|
419
|
-
` telegram.chatId: ${cfg.chatId ?? "(unset)"}\n` +
|
|
420
|
-
` discord.botToken: ${maskToken(cfg.discord.botToken)}\n` +
|
|
421
|
-
` discord.channelId: ${cfg.discord.channelId ?? "(unset)"}\n` +
|
|
422
|
-
` slack.botToken: ${maskToken(cfg.slack.botToken)}\n` +
|
|
423
|
-
` slack.channelId: ${cfg.slack.channelId ?? "(unset)"}\n` +
|
|
424
|
-
` redact: ${cfg.redact}\n`,
|
|
450
|
+
`${chalk.bold("Notifications")}\n${formatNotificationStatusReport(report).split("\n").slice(1).join("\n")}\n`,
|
|
425
451
|
);
|
|
426
452
|
}
|
|
427
453
|
|
|
454
|
+
async function runHealth(deps: NotifyCommandDeps, cmd: NotifyCommandArgs): Promise<void> {
|
|
455
|
+
const settings = await getSettings(deps);
|
|
456
|
+
const report = await checkNotificationHealth({
|
|
457
|
+
settings,
|
|
458
|
+
probe: cmd.probe,
|
|
459
|
+
deps: { fetchImpl: deps.fetchImpl, apiBase: deps.apiBase },
|
|
460
|
+
});
|
|
461
|
+
process.stdout.write(`${formatNotificationHealthReport(report)}\n`);
|
|
462
|
+
if (report.overall === "error" && deps.setExitCode) deps.setExitCode(1);
|
|
463
|
+
else if (report.overall === "error") process.exitCode = 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async function runTest(deps: NotifyCommandDeps, cmd: NotifyCommandArgs): Promise<void> {
|
|
467
|
+
const settings = await getSettings(deps);
|
|
468
|
+
const result = await sendNotificationTest({
|
|
469
|
+
settings,
|
|
470
|
+
text: cmd.message,
|
|
471
|
+
deps: { fetchImpl: deps.fetchImpl, apiBase: deps.apiBase },
|
|
472
|
+
});
|
|
473
|
+
process.stdout.write(`${formatNotificationTestResult(result)}\n`);
|
|
474
|
+
if (!result.ok && deps.setExitCode) deps.setExitCode(1);
|
|
475
|
+
else if (!result.ok) process.exitCode = 1;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async function runRecovery(deps: NotifyCommandDeps): Promise<void> {
|
|
479
|
+
const settings = await getSettings(deps);
|
|
480
|
+
const report = await recoverNotifications({ settings });
|
|
481
|
+
process.stdout.write(`${formatNotificationRecoveryReport(report)}\n`);
|
|
482
|
+
}
|
|
483
|
+
|
|
428
484
|
async function waitForPrivateChat(
|
|
429
485
|
fetchImpl: typeof fetch,
|
|
430
486
|
apiBase: string,
|
|
@@ -514,15 +570,23 @@ ${chalk.bold("Usage:")}
|
|
|
514
570
|
${APP_NAME} notify setup
|
|
515
571
|
${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
|
|
516
572
|
${APP_NAME} notify status
|
|
573
|
+
${APP_NAME} notify health [--probe]
|
|
574
|
+
${APP_NAME} notify test [--message <text>]
|
|
575
|
+
${APP_NAME} notify recovery
|
|
517
576
|
|
|
518
577
|
${chalk.bold("Subcommands:")}
|
|
519
578
|
setup Pair a Telegram bot token with a private chat and verify Threaded Mode capability
|
|
520
579
|
status Show notification configuration without secrets
|
|
580
|
+
health Report config, daemon-ownership and endpoint health (--probe adds a Telegram reachability check)
|
|
581
|
+
test Send a one-off test notification through the configured Telegram adapter
|
|
582
|
+
recovery Clear dead-owner daemon locks and stale per-session endpoint files (never touches a live owner)
|
|
521
583
|
|
|
522
584
|
${chalk.bold("Examples:")}
|
|
523
585
|
${APP_NAME} notify setup
|
|
524
|
-
${APP_NAME} notify setup --token <botToken> --chat-id <chatId> [--redact]
|
|
525
586
|
${APP_NAME} notify status
|
|
587
|
+
${APP_NAME} notify health --probe
|
|
588
|
+
${APP_NAME} notify test --message "hello from skc"
|
|
589
|
+
${APP_NAME} notify recovery
|
|
526
590
|
|
|
527
591
|
${chalk.bold("Threaded Mode:")}
|
|
528
592
|
SKC uses Telegram private-chat topics for per-session threads. Setup verifies the bot
|
|
@@ -1,49 +1,32 @@
|
|
|
1
1
|
import { ProcessTerminal, TUI } from "@sayknow-cli/tui";
|
|
2
|
-
import { SessionSelectorComponent } from "../modes/components/session-selector";
|
|
3
|
-
import type
|
|
2
|
+
import { type SessionSelectionResult, SessionSelectorComponent } from "../modes/components/session-selector";
|
|
3
|
+
import { type SessionInfo, SessionManager } from "../session/session-manager";
|
|
4
4
|
import { FileSessionStorage } from "../session/session-storage";
|
|
5
5
|
|
|
6
|
-
/** Show TUI session
|
|
7
|
-
export async function selectSession(sessions: SessionInfo[]): Promise<
|
|
8
|
-
const { promise, resolve } = Promise.withResolvers<
|
|
6
|
+
/** Show the read-only TUI session picker and return the user's consent intent. */
|
|
7
|
+
export async function selectSession(sessions: SessionInfo[]): Promise<SessionSelectionResult> {
|
|
8
|
+
const { promise, resolve } = Promise.withResolvers<SessionSelectionResult>();
|
|
9
9
|
const ui = new TUI(new ProcessTerminal());
|
|
10
|
-
let resolved = false;
|
|
11
10
|
const storage = new FileSessionStorage();
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
resolved = true;
|
|
19
|
-
ui.stop();
|
|
20
|
-
resolve(path);
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
() => {
|
|
24
|
-
if (!resolved) {
|
|
25
|
-
resolved = true;
|
|
26
|
-
ui.stop();
|
|
27
|
-
resolve(null);
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
() => {
|
|
31
|
-
if (!resolved) {
|
|
32
|
-
resolved = true;
|
|
33
|
-
ui.stop();
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
async (session: SessionInfo) => {
|
|
38
|
-
// Delete handler - SessionList will show confirmation internally
|
|
39
|
-
await storage.deleteSessionWithArtifacts(session.path);
|
|
40
|
-
return true;
|
|
41
|
-
},
|
|
42
|
-
);
|
|
43
|
-
return selector;
|
|
11
|
+
let settled = false;
|
|
12
|
+
const settle = (selection: SessionSelectionResult): void => {
|
|
13
|
+
if (settled) return;
|
|
14
|
+
settled = true;
|
|
15
|
+
ui.stop();
|
|
16
|
+
resolve(selection);
|
|
44
17
|
};
|
|
45
|
-
|
|
46
|
-
|
|
18
|
+
const selector = new SessionSelectorComponent(
|
|
19
|
+
sessions,
|
|
20
|
+
() => {},
|
|
21
|
+
() => settle({ kind: "cancelled" }),
|
|
22
|
+
() => settle({ kind: "cancelled" }),
|
|
23
|
+
async session => {
|
|
24
|
+
await storage.deleteSessionWithArtifacts(session.path);
|
|
25
|
+
return true;
|
|
26
|
+
},
|
|
27
|
+
SessionManager.inspectSessionTailReadOnly,
|
|
28
|
+
settle,
|
|
29
|
+
);
|
|
47
30
|
selector.setOnRequestRender(() => ui.requestRender());
|
|
48
31
|
ui.addChild(selector);
|
|
49
32
|
ui.setFocus(selector);
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -45,7 +45,6 @@ export interface PackageManagerUpdateOptions {
|
|
|
45
45
|
expectedVersion: string;
|
|
46
46
|
runInstall: PackageManagerUpdateRunner;
|
|
47
47
|
verifyInstalledRuntime: (expectedVersion: string) => Promise<InstalledVersionVerification>;
|
|
48
|
-
printVerificationResult?: (expectedVersion: string) => Promise<void>;
|
|
49
48
|
printRecoveredVerification?: (expectedVersion: string) => void;
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -122,8 +121,11 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
|
122
121
|
return isPathInDirectoryLexical(resolvedFile, dirReal);
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
type PackageManagerTarget = { manager: "npm"; packageName: string };
|
|
126
|
-
type UpdateTarget =
|
|
124
|
+
export type PackageManagerTarget = { manager: "npm"; packageName: string };
|
|
125
|
+
export type UpdateTarget =
|
|
126
|
+
| { method: "bun" }
|
|
127
|
+
| { method: "npm"; packageName: string }
|
|
128
|
+
| { method: "binary"; path: string };
|
|
127
129
|
|
|
128
130
|
type PathPlatform = NodeJS.Platform;
|
|
129
131
|
type PackageExists = (packageName: string, packageRoot: string) => boolean;
|
|
@@ -423,19 +425,6 @@ export function formatVerificationFailureForTest(
|
|
|
423
425
|
return formatVerificationFailure(result, expectedVersion);
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
/**
|
|
427
|
-
* Print post-update verification result.
|
|
428
|
-
*/
|
|
429
|
-
async function printVerification(expectedVersion: string): Promise<void> {
|
|
430
|
-
const result = await verifyInstalledRuntime(expectedVersion);
|
|
431
|
-
if (result.ok) {
|
|
432
|
-
printSuccessfulVerification(expectedVersion);
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
console.log(chalk.yellow(`\nWarning: ${formatVerificationFailure(result, expectedVersion)}`));
|
|
436
|
-
console.log(chalk.yellow(formatManualUpdateInstructions()));
|
|
437
|
-
}
|
|
438
|
-
|
|
439
428
|
async function unlinkIfExists(filePath: string): Promise<void> {
|
|
440
429
|
try {
|
|
441
430
|
await fs.promises.unlink(filePath);
|
|
@@ -499,6 +488,14 @@ function formatPackageManagerInstallFailure(
|
|
|
499
488
|
return `${managerName} install failed with exit code ${result.exitCode ?? "unknown"}${outputSuffix}. ${formatVerificationFailure(verification, expectedVersion)}`;
|
|
500
489
|
}
|
|
501
490
|
|
|
491
|
+
function formatPackageManagerVerificationFailure(
|
|
492
|
+
managerName: string,
|
|
493
|
+
verification: InstalledVersionVerification,
|
|
494
|
+
expectedVersion: string,
|
|
495
|
+
): string {
|
|
496
|
+
return `${managerName} install exited successfully, but the selected ${APP_NAME} runtime failed verification: ${formatVerificationFailure(verification, expectedVersion)}`;
|
|
497
|
+
}
|
|
498
|
+
|
|
502
499
|
export async function runPackageManagerUpdateForTest(
|
|
503
500
|
options: PackageManagerUpdateOptions,
|
|
504
501
|
): Promise<InstalledVersionVerification> {
|
|
@@ -508,8 +505,14 @@ export async function runPackageManagerUpdateForTest(
|
|
|
508
505
|
async function updateViaPackageManager(options: PackageManagerUpdateOptions): Promise<InstalledVersionVerification> {
|
|
509
506
|
const result = await options.runInstall(options.expectedVersion);
|
|
510
507
|
if (result.exitCode === 0) {
|
|
511
|
-
await
|
|
512
|
-
|
|
508
|
+
const verification = await options.verifyInstalledRuntime(options.expectedVersion);
|
|
509
|
+
if (!verification.ok) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
formatPackageManagerVerificationFailure(options.managerName, verification, options.expectedVersion),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
printSuccessfulVerification(options.expectedVersion);
|
|
515
|
+
return verification;
|
|
513
516
|
}
|
|
514
517
|
|
|
515
518
|
const verification = await options.verifyInstalledRuntime(options.expectedVersion);
|
|
@@ -661,16 +664,42 @@ async function updateViaBinaryAt(targetPath: string, expectedVersion: string): P
|
|
|
661
664
|
/**
|
|
662
665
|
* Run the update command.
|
|
663
666
|
*/
|
|
664
|
-
export
|
|
667
|
+
export interface UpdateCommandDependencies {
|
|
668
|
+
getLatestRelease?: () => Promise<ReleaseInfo>;
|
|
669
|
+
resolveUpdateTarget?: () => Promise<UpdateTarget>;
|
|
670
|
+
performUpdate?: (target: UpdateTarget, expectedVersion: string) => Promise<void>;
|
|
671
|
+
refreshInstalledDefaultSkills?: () => Promise<void>;
|
|
672
|
+
exit?: (code: number) => never;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async function performUpdate(target: UpdateTarget, expectedVersion: string): Promise<void> {
|
|
676
|
+
if (target.method === "bun") {
|
|
677
|
+
await updateViaBun(expectedVersion);
|
|
678
|
+
} else if (target.method === "npm") {
|
|
679
|
+
await updateViaNpm(target.packageName, expectedVersion);
|
|
680
|
+
} else {
|
|
681
|
+
await updateViaBinaryAt(target.path, expectedVersion);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export async function runUpdateCommand(
|
|
686
|
+
opts: { force: boolean; check: boolean },
|
|
687
|
+
deps: UpdateCommandDependencies = {},
|
|
688
|
+
): Promise<void> {
|
|
689
|
+
const lookupRelease = deps.getLatestRelease ?? getLatestRelease;
|
|
690
|
+
const resolveTarget = deps.resolveUpdateTarget ?? resolveUpdateTarget;
|
|
691
|
+
const update = deps.performUpdate ?? performUpdate;
|
|
692
|
+
const refreshDefaults = deps.refreshInstalledDefaultSkills ?? refreshInstalledDefaultSkills;
|
|
693
|
+
const exit = deps.exit ?? process.exit;
|
|
694
|
+
|
|
665
695
|
console.log(chalk.dim(`Current version: ${VERSION}`));
|
|
666
696
|
|
|
667
|
-
// Check for updates
|
|
668
697
|
let release: ReleaseInfo;
|
|
669
698
|
try {
|
|
670
|
-
release = await
|
|
699
|
+
release = await lookupRelease();
|
|
671
700
|
} catch (err) {
|
|
672
701
|
console.error(chalk.red(`Failed to check for updates: ${err}`));
|
|
673
|
-
|
|
702
|
+
return exit(1);
|
|
674
703
|
}
|
|
675
704
|
|
|
676
705
|
const comparison = compareVersions(release.version, VERSION);
|
|
@@ -686,27 +715,17 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
686
715
|
console.log(chalk.yellow(`Forcing reinstall of ${release.version}`));
|
|
687
716
|
}
|
|
688
717
|
|
|
689
|
-
if (opts.check)
|
|
690
|
-
// Just check, don't install
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
718
|
+
if (opts.check) return;
|
|
693
719
|
|
|
694
|
-
// Choose update method based on the prioritized skc binary in PATH
|
|
695
720
|
try {
|
|
696
|
-
const target = await
|
|
697
|
-
|
|
698
|
-
await updateViaBun(release.version);
|
|
699
|
-
} else if (target.method === "npm") {
|
|
700
|
-
await updateViaNpm(target.packageName, release.version);
|
|
701
|
-
} else {
|
|
702
|
-
await updateViaBinaryAt(target.path, release.version);
|
|
703
|
-
}
|
|
721
|
+
const target = await resolveTarget();
|
|
722
|
+
await update(target, release.version);
|
|
704
723
|
} catch (err) {
|
|
705
724
|
console.error(chalk.red(`Update failed: ${err}`));
|
|
706
|
-
|
|
725
|
+
return exit(1);
|
|
707
726
|
}
|
|
708
727
|
|
|
709
|
-
await
|
|
728
|
+
await refreshDefaults();
|
|
710
729
|
}
|
|
711
730
|
|
|
712
731
|
/**
|