@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
|
@@ -14,6 +14,9 @@ export const SKC_TMUX_BRANCH_SLUG_OPTION = "@skc-branch-slug";
|
|
|
14
14
|
export const SKC_TMUX_PROJECT_OPTION = "@skc-project";
|
|
15
15
|
export const SKC_TMUX_SESSION_ID_OPTION = "@skc-session-id";
|
|
16
16
|
export const SKC_TMUX_SESSION_STATE_FILE_OPTION = "@skc-session-state-file";
|
|
17
|
+
export const SKC_TMUX_OWNER_GENERATION_OPTION = "@skc-owner-generation";
|
|
18
|
+
export const SKC_TMUX_OWNER_SERVER_KEY_OPTION = "@skc-owner-server-key";
|
|
19
|
+
|
|
17
20
|
export const SKC_TMUX_VERSION_OPTION = "@skc-version";
|
|
18
21
|
export const SKC_PSMUX_PROFILE_FORCE_ENV = "SKC_PSMUX_PROFILE_FORCE";
|
|
19
22
|
|
|
@@ -158,15 +161,13 @@ export function buildSkcTmuxRequiredProfileCommands(
|
|
|
158
161
|
project?: string | null;
|
|
159
162
|
sessionId?: string | null;
|
|
160
163
|
sessionStateFile?: string | null;
|
|
164
|
+
ownerGeneration?: string | null;
|
|
165
|
+
ownerServerKey?: string | null;
|
|
161
166
|
version?: string | null;
|
|
162
167
|
} = {},
|
|
163
168
|
): SkcTmuxProfileCommand[] {
|
|
164
|
-
const commands: SkcTmuxProfileCommand[] = [
|
|
165
|
-
|
|
166
|
-
description: "mark SKC tmux ownership",
|
|
167
|
-
args: ["set-option", "-t", target, SKC_TMUX_PROFILE_OPTION, SKC_TMUX_PROFILE_VALUE],
|
|
168
|
-
},
|
|
169
|
-
];
|
|
169
|
+
const commands: SkcTmuxProfileCommand[] = [];
|
|
170
|
+
|
|
170
171
|
if (metadata.branch)
|
|
171
172
|
commands.push({
|
|
172
173
|
description: "record SKC branch identity",
|
|
@@ -192,11 +193,26 @@ export function buildSkcTmuxRequiredProfileCommands(
|
|
|
192
193
|
description: "record SKC session state marker",
|
|
193
194
|
args: ["set-option", "-t", target, SKC_TMUX_SESSION_STATE_FILE_OPTION, metadata.sessionStateFile],
|
|
194
195
|
});
|
|
196
|
+
if (metadata.ownerGeneration)
|
|
197
|
+
commands.push({
|
|
198
|
+
description: "record SKC owner generation",
|
|
199
|
+
args: ["set-option", "-t", target, SKC_TMUX_OWNER_GENERATION_OPTION, metadata.ownerGeneration],
|
|
200
|
+
});
|
|
201
|
+
if (metadata.ownerServerKey)
|
|
202
|
+
commands.push({
|
|
203
|
+
description: "record SKC owner server key",
|
|
204
|
+
args: ["set-option", "-t", target, SKC_TMUX_OWNER_SERVER_KEY_OPTION, metadata.ownerServerKey],
|
|
205
|
+
});
|
|
206
|
+
|
|
195
207
|
if (metadata.version)
|
|
196
208
|
commands.push({
|
|
197
209
|
description: "record SKC version identity",
|
|
198
210
|
args: ["set-option", "-t", target, SKC_TMUX_VERSION_OPTION, metadata.version],
|
|
199
211
|
});
|
|
212
|
+
commands.push({
|
|
213
|
+
description: "mark SKC tmux ownership",
|
|
214
|
+
args: ["set-option", "-t", target, SKC_TMUX_PROFILE_OPTION, SKC_TMUX_PROFILE_VALUE],
|
|
215
|
+
});
|
|
200
216
|
return commands;
|
|
201
217
|
}
|
|
202
218
|
|
|
@@ -219,6 +235,8 @@ export function buildSkcTmuxProfileCommands(
|
|
|
219
235
|
project?: string | null;
|
|
220
236
|
sessionId?: string | null;
|
|
221
237
|
sessionStateFile?: string | null;
|
|
238
|
+
ownerGeneration?: string | null;
|
|
239
|
+
ownerServerKey?: string | null;
|
|
222
240
|
version?: string | null;
|
|
223
241
|
} = {},
|
|
224
242
|
opts: { platform?: NodeJS.Platform; tmuxCommand?: string } = {},
|
|
@@ -260,7 +278,8 @@ export function buildSkcTmuxProfileCommands(
|
|
|
260
278
|
tmuxName.endsWith("/pmux") ||
|
|
261
279
|
tmuxName.endsWith("\\psmux") ||
|
|
262
280
|
tmuxName.endsWith("\\pmux");
|
|
263
|
-
const
|
|
281
|
+
const forcePsmuxProfile = env[SKC_PSMUX_PROFILE_FORCE_ENV] === "true" || env[SKC_PSMUX_PROFILE_FORCE_ENV] === "1";
|
|
282
|
+
const dropUx = isPsmuxClass && !forcePsmuxProfile;
|
|
264
283
|
if (dropUx) {
|
|
265
284
|
return commands.filter(command => {
|
|
266
285
|
const flag = command.args[0];
|
|
@@ -22,6 +22,37 @@ const STORE = "tmux_sessions" as const;
|
|
|
22
22
|
const TOCTOU_SKIP = "tmux_revalidation_failed_or_became_live";
|
|
23
23
|
const ORPHAN_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
24
24
|
|
|
25
|
+
type CollectedTmuxIdentity = {
|
|
26
|
+
nativeSessionId: string;
|
|
27
|
+
ownerGeneration: string;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
sessionStateFile: string;
|
|
30
|
+
project: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const collectedIdentities = new WeakMap<GcRecord, CollectedTmuxIdentity>();
|
|
35
|
+
|
|
36
|
+
function collectedIdentity(session: SkcTmuxSessionStatus): CollectedTmuxIdentity | undefined {
|
|
37
|
+
if (
|
|
38
|
+
!session.nativeSessionId ||
|
|
39
|
+
!session.ownerGeneration ||
|
|
40
|
+
!session.sessionId ||
|
|
41
|
+
!session.sessionStateFile ||
|
|
42
|
+
!session.project ||
|
|
43
|
+
!session.createdAt
|
|
44
|
+
)
|
|
45
|
+
return undefined;
|
|
46
|
+
return {
|
|
47
|
+
nativeSessionId: session.nativeSessionId,
|
|
48
|
+
ownerGeneration: session.ownerGeneration,
|
|
49
|
+
sessionId: session.sessionId,
|
|
50
|
+
sessionStateFile: session.sessionStateFile,
|
|
51
|
+
project: session.project,
|
|
52
|
+
createdAt: session.createdAt,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
25
56
|
function pathExists(path: string): boolean {
|
|
26
57
|
try {
|
|
27
58
|
return fs.existsSync(path);
|
|
@@ -168,15 +199,21 @@ function classifyUntaggedSession(session: SkcTmuxSessionStatus): GcRecord {
|
|
|
168
199
|
return unclassifiedRecord(session.name, "untagged_tmux_session");
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
async function revalidateRemovable(
|
|
202
|
+
async function revalidateRemovable(
|
|
203
|
+
record: GcRecord,
|
|
204
|
+
identity: CollectedTmuxIdentity,
|
|
205
|
+
env: NodeJS.ProcessEnv,
|
|
206
|
+
): Promise<boolean> {
|
|
172
207
|
const tags = readTmuxSessionTagsForGc(record.id, env);
|
|
173
208
|
if (
|
|
174
|
-
tags.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
209
|
+
tags.nativeSessionId !== identity.nativeSessionId ||
|
|
210
|
+
tags.ownerGeneration !== identity.ownerGeneration ||
|
|
211
|
+
tags.sessionId !== identity.sessionId ||
|
|
212
|
+
tags.sessionStateFile !== identity.sessionStateFile ||
|
|
213
|
+
tags.project !== identity.project ||
|
|
214
|
+
tags.createdAt !== identity.createdAt
|
|
215
|
+
)
|
|
178
216
|
return false;
|
|
179
|
-
}
|
|
180
217
|
if (tags.attached || (tags.panePids?.length ?? 0) > 0) return false;
|
|
181
218
|
if (tags.profile !== SKC_TMUX_PROFILE_VALUE) return false;
|
|
182
219
|
return await hasTerminalRuntimeMarker(tags);
|
|
@@ -205,7 +242,12 @@ export const tmuxSessionsGcAdapter: GcStoreAdapter = {
|
|
|
205
242
|
|
|
206
243
|
for (const session of sessions.tagged) {
|
|
207
244
|
try {
|
|
208
|
-
|
|
245
|
+
const record = await classifyTaggedSession(session);
|
|
246
|
+
records.push(record);
|
|
247
|
+
if (record.removable) {
|
|
248
|
+
const identity = collectedIdentity(session);
|
|
249
|
+
if (identity) collectedIdentities.set(record, identity);
|
|
250
|
+
}
|
|
209
251
|
} catch (error) {
|
|
210
252
|
errors.push({
|
|
211
253
|
store: STORE,
|
|
@@ -227,10 +269,11 @@ export const tmuxSessionsGcAdapter: GcStoreAdapter = {
|
|
|
227
269
|
return { removed: false, skipped: "not_removable_tmux_session" };
|
|
228
270
|
}
|
|
229
271
|
try {
|
|
230
|
-
|
|
272
|
+
const identity = collectedIdentities.get(record);
|
|
273
|
+
if (!identity || !(await revalidateRemovable(record, identity, ctx.env))) {
|
|
231
274
|
return { removed: false, skipped: TOCTOU_SKIP };
|
|
232
275
|
}
|
|
233
|
-
removeSkcTmuxSession(record.id, ctx.env);
|
|
276
|
+
removeSkcTmuxSession(record.id, ctx.env, identity);
|
|
234
277
|
return { removed: true };
|
|
235
278
|
} catch (error) {
|
|
236
279
|
return { removed: false, error: error instanceof Error ? error.message : String(error) };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** Internal JSON-line facade for the tmux owner-isolation contract. */
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import {
|
|
4
|
+
type BootstrapRequest,
|
|
5
|
+
bootstrapTmuxOwnerIsolation,
|
|
6
|
+
classifyCgroup,
|
|
7
|
+
type ObserveTerminalRequest,
|
|
8
|
+
type OwnerIsolationProbe,
|
|
9
|
+
observeOwnerTerminal,
|
|
10
|
+
type PlanRequest,
|
|
11
|
+
type PublishGenerationRequest,
|
|
12
|
+
parseOwnerIsolationRequest,
|
|
13
|
+
planTmuxOwnerIsolation,
|
|
14
|
+
publishOwnerGenerationSync,
|
|
15
|
+
serializeOwnerIsolationResponse,
|
|
16
|
+
TMUX_OWNER_ISOLATION_MAX_LINE_BYTES,
|
|
17
|
+
type TmuxServerProof,
|
|
18
|
+
} from "./tmux-owner-isolation";
|
|
19
|
+
|
|
20
|
+
/** Matches the sole argv shape allowed to enter the owner-isolation JSON-line protocol. */
|
|
21
|
+
export function isTmuxOwnerIsolationCliArgv(argv: readonly string[]): boolean {
|
|
22
|
+
return argv.length === 1 && argv[0] === "--internal-tmux-owner-isolation";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function readCgroup(pid = "self"): Promise<string | null> {
|
|
26
|
+
try {
|
|
27
|
+
return await fs.readFile(`/proc/${pid}/cgroup`, "utf8");
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readProcessStartTime(pid: number): Promise<string | null> {
|
|
34
|
+
try {
|
|
35
|
+
const stat = await fs.readFile(`/proc/${pid}/stat`, "utf8");
|
|
36
|
+
return (
|
|
37
|
+
stat
|
|
38
|
+
.slice(stat.lastIndexOf(")") + 2)
|
|
39
|
+
.trim()
|
|
40
|
+
.split(/\s+/)[19] ?? null
|
|
41
|
+
);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isKnownNoServerDiagnostic(stderr: string): boolean {
|
|
48
|
+
const diagnostic = stderr.trim().toLowerCase();
|
|
49
|
+
return (
|
|
50
|
+
diagnostic.length > 0 &&
|
|
51
|
+
diagnostic.length <= 512 &&
|
|
52
|
+
/no server running|failed to connect to server|error connecting to/.test(diagnostic)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function tmuxServerProof(socketKey: string, tmuxControlArgv?: string[]): Promise<TmuxServerProof> {
|
|
57
|
+
void socketKey;
|
|
58
|
+
if (!tmuxControlArgv?.length) return { state: "unverifiable" };
|
|
59
|
+
const controlArgv = tmuxControlArgv;
|
|
60
|
+
const subprocess = Bun.spawnSync([...controlArgv, "list-sessions", "-F", "#{pid}\t#{session_name}"], {
|
|
61
|
+
stdout: "pipe",
|
|
62
|
+
stderr: "pipe",
|
|
63
|
+
});
|
|
64
|
+
const stderr = Buffer.from(subprocess.stderr).toString();
|
|
65
|
+
if (subprocess.exitCode !== 0)
|
|
66
|
+
return isKnownNoServerDiagnostic(stderr) ? { state: "absent" } : { state: "unverifiable" };
|
|
67
|
+
const lines = Buffer.from(subprocess.stdout)
|
|
68
|
+
.toString()
|
|
69
|
+
.split("\n")
|
|
70
|
+
.map(line => line.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
const [pidText] = lines[0]?.split("\t") ?? [];
|
|
73
|
+
const pid = Number(pidText);
|
|
74
|
+
const sessionNames = lines.map(line => line.split("\t")[1]).filter((name): name is string => Boolean(name));
|
|
75
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return { state: "unverifiable" };
|
|
76
|
+
if (process.platform !== "linux") {
|
|
77
|
+
return {
|
|
78
|
+
state: "safe",
|
|
79
|
+
pid,
|
|
80
|
+
startTime: "not_applicable",
|
|
81
|
+
cgroup: { classification: "not_applicable" },
|
|
82
|
+
sessionNames,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const cgroup = classifyCgroup({ platform: process.platform, cgroupText: await readCgroup(String(pid)) });
|
|
86
|
+
const startTime = await readProcessStartTime(pid);
|
|
87
|
+
if (!startTime) return { state: "unverifiable", pid, cgroup, sessionNames };
|
|
88
|
+
return {
|
|
89
|
+
state:
|
|
90
|
+
cgroup.classification === "safe"
|
|
91
|
+
? "safe"
|
|
92
|
+
: cgroup.classification === "unsafe_service"
|
|
93
|
+
? "unsafe"
|
|
94
|
+
: "unverifiable",
|
|
95
|
+
pid,
|
|
96
|
+
startTime,
|
|
97
|
+
cgroup,
|
|
98
|
+
sessionNames,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function probe(): OwnerIsolationProbe {
|
|
103
|
+
return {
|
|
104
|
+
readCallerCgroup: () => readCgroup(),
|
|
105
|
+
probeServer: async (socketKey, tmuxArgv) => tmuxServerProof(socketKey, tmuxArgv),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cliFailure(diagnostic: string): string {
|
|
110
|
+
return serializeOwnerIsolationResponse({ schema_version: 1, ok: false, code: "scope_unavailable", diagnostic });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Reads exactly one bounded JSON line and writes exactly one JSON response line. */
|
|
114
|
+
export async function runTmuxOwnerIsolationCli(stdin: string): Promise<string> {
|
|
115
|
+
const line = stdin.endsWith("\n") ? stdin.slice(0, -1) : stdin;
|
|
116
|
+
if (line.includes("\n")) return cliFailure("invalid_json_line");
|
|
117
|
+
const request = parseOwnerIsolationRequest(line);
|
|
118
|
+
if (!request) return cliFailure("invalid_json_line");
|
|
119
|
+
if (request.op === "plan")
|
|
120
|
+
return serializeOwnerIsolationResponse(await planTmuxOwnerIsolation(request as PlanRequest, probe()));
|
|
121
|
+
if (request.op === "bootstrap") {
|
|
122
|
+
const bootstrap = request as BootstrapRequest;
|
|
123
|
+
return serializeOwnerIsolationResponse(
|
|
124
|
+
await bootstrapTmuxOwnerIsolation(bootstrap, {
|
|
125
|
+
readSelfCgroup: () => readCgroup(),
|
|
126
|
+
spawn: argv => {
|
|
127
|
+
const result = Bun.spawnSync(argv, { stdin: "ignore", stdout: "pipe", stderr: "ignore" });
|
|
128
|
+
return { exitCode: result.exitCode, stdout: Buffer.from(result.stdout).toString() };
|
|
129
|
+
},
|
|
130
|
+
probeServer: async (socketKey, tmuxControlArgv) => tmuxServerProof(socketKey, tmuxControlArgv),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (request.op === "publish_generation") {
|
|
135
|
+
try {
|
|
136
|
+
return serializeOwnerIsolationResponse(publishOwnerGenerationSync(request as PublishGenerationRequest));
|
|
137
|
+
} catch {
|
|
138
|
+
return cliFailure("generation_publication_failed");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
return serializeOwnerIsolationResponse(await observeOwnerTerminal(request as ObserveTerminalRequest));
|
|
143
|
+
} catch {
|
|
144
|
+
return cliFailure("terminal_observation_failed");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const MAX_JSON_LINE_BYTES = TMUX_OWNER_ISOLATION_MAX_LINE_BYTES;
|
|
149
|
+
|
|
150
|
+
async function readOneBoundedJsonLine(): Promise<string | null> {
|
|
151
|
+
const reader = Bun.stdin.stream().getReader();
|
|
152
|
+
const bytes: number[] = [];
|
|
153
|
+
let complete = false;
|
|
154
|
+
try {
|
|
155
|
+
while (true) {
|
|
156
|
+
const { done, value } = await reader.read();
|
|
157
|
+
if (done) break;
|
|
158
|
+
for (const byte of value) {
|
|
159
|
+
if (complete || (byte !== 0x0a && bytes.length >= MAX_JSON_LINE_BYTES)) return null;
|
|
160
|
+
if (byte === 0x0a) {
|
|
161
|
+
complete = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
bytes.push(byte);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} finally {
|
|
168
|
+
reader.releaseLock();
|
|
169
|
+
}
|
|
170
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function runTmuxOwnerIsolationCliFromStdin(): Promise<void> {
|
|
174
|
+
const stdin = await readOneBoundedJsonLine();
|
|
175
|
+
const output = stdin === null ? cliFailure("invalid_json_line") : await runTmuxOwnerIsolationCli(stdin);
|
|
176
|
+
process.stdout.write(`${output}\n`);
|
|
177
|
+
const request = stdin === null ? null : parseOwnerIsolationRequest(stdin);
|
|
178
|
+
if (request?.op === "bootstrap") {
|
|
179
|
+
try {
|
|
180
|
+
if (JSON.parse(output).ok !== true) process.exitCode = 1;
|
|
181
|
+
} catch {
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|