@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
|
@@ -118,6 +118,20 @@ function findWorktreeByPath(entries: GitWorktreeEntry[], worktreePath: string):
|
|
|
118
118
|
return entries.find(entry => path.resolve(entry.path) === resolved) ?? null;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function describeWorktreeEntry(entry: GitWorktreeEntry): string {
|
|
122
|
+
return entry.detached ? `detached HEAD ${entry.head}` : (entry.branchRef ?? `HEAD ${entry.head}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatWorktreeTargetMismatch(plan: SkcLaunchWorktreePlan, existing: GitWorktreeEntry): string {
|
|
126
|
+
const expected = plan.detached ? `detached HEAD ${plan.baseRef}` : `branch refs/heads/${plan.branchName ?? ""}`;
|
|
127
|
+
return [
|
|
128
|
+
`worktree_target_mismatch:${plan.worktreePath}`,
|
|
129
|
+
`SKC launch worktree target is already registered for ${describeWorktreeEntry(existing)}, but this launch expects ${expected}.`,
|
|
130
|
+
`Path: ${plan.worktreePath}`,
|
|
131
|
+
"Refusing to delete or reuse the conflicting worktree automatically. Safe remediation: inspect the path, commit/stash any work, then remove or prune the stale worktree with git worktree remove <path> when it is no longer needed, or choose a different --worktree name.",
|
|
132
|
+
].join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
121
135
|
function hasBranchInUse(entries: GitWorktreeEntry[], branchName: string, worktreePath: string): boolean {
|
|
122
136
|
const expectedRef = `refs/heads/${branchName}`;
|
|
123
137
|
const resolvedPath = path.resolve(worktreePath);
|
|
@@ -228,7 +242,7 @@ export function ensureLaunchWorktree(
|
|
|
228
242
|
let dirty = isWorktreeDirty(plan.worktreePath);
|
|
229
243
|
if (plan.detached) {
|
|
230
244
|
if (!existingAtPath.detached) {
|
|
231
|
-
throw new Error(
|
|
245
|
+
throw new Error(formatWorktreeTargetMismatch(plan, existingAtPath));
|
|
232
246
|
}
|
|
233
247
|
if (existingAtPath.head !== plan.baseRef) {
|
|
234
248
|
if (dirty) throw new Error(`worktree_dirty:${plan.worktreePath}`);
|
|
@@ -236,7 +250,7 @@ export function ensureLaunchWorktree(
|
|
|
236
250
|
dirty = false;
|
|
237
251
|
}
|
|
238
252
|
} else if (existingAtPath.branchRef !== expectedBranchRef) {
|
|
239
|
-
throw new Error(
|
|
253
|
+
throw new Error(formatWorktreeTargetMismatch(plan, existingAtPath));
|
|
240
254
|
}
|
|
241
255
|
return {
|
|
242
256
|
...plan,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type RalplanIndexRow,
|
|
12
12
|
summarizeRalplanIndex,
|
|
13
13
|
} from "./ledger-event-renderer";
|
|
14
|
-
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
14
|
+
import { isRestrictedRoleAgentBash, SKC_RALPLAN_ARTIFACT_ENV } from "./restricted-role-agent-bash";
|
|
15
15
|
import { modeStatePath, sessionPlansDir } from "./session-layout";
|
|
16
16
|
import { resolveSkcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
|
|
17
17
|
import { migrateWorkflowState } from "./state-migrations";
|
|
@@ -35,8 +35,9 @@ import {
|
|
|
35
35
|
* that lives in the bundled `/skill:ralplan` skill — but it accepts every documented flag so
|
|
36
36
|
* scripted users see a useful response and the active run is visible to the TUI.
|
|
37
37
|
*
|
|
38
|
-
* 2. **Artifact write**: `skc ralplan --write --stage <type> --stage_n <N>
|
|
39
|
-
* <path-or-string>
|
|
38
|
+
* 2. **Artifact write**: `skc ralplan --write --stage <type> --stage_n <N>
|
|
39
|
+
* (--artifact <path-or-string> | --artifact-env SKC_RALPLAN_ARTIFACT)
|
|
40
|
+
* [--run-id <id>] [--session-id <id>] [--json]` persists Planner / Architect
|
|
40
41
|
* / Critic / revision / post-interview / ADR / final markdown under `.skc/plans/ralplan/<run-id>/`, maintains
|
|
41
42
|
* an `index.jsonl` audit log, copies `final` stages to `pending-approval.md`, and advances
|
|
42
43
|
* the HUD chip to reflect the latest persisted stage.
|
|
@@ -81,6 +82,7 @@ const VALUE_FLAGS = new Set([
|
|
|
81
82
|
"--stage",
|
|
82
83
|
"--stage_n",
|
|
83
84
|
"--artifact",
|
|
85
|
+
"--artifact-env",
|
|
84
86
|
"--run-id",
|
|
85
87
|
"--session-id",
|
|
86
88
|
"--architect",
|
|
@@ -409,8 +411,16 @@ async function resolveArtifactArgs(args: readonly string[], cwd: string): Promis
|
|
|
409
411
|
const stageN = parseStageN(flagValue(args, "--stage_n"));
|
|
410
412
|
|
|
411
413
|
const rawArtifact = flagValue(args, "--artifact");
|
|
412
|
-
|
|
413
|
-
|
|
414
|
+
const artifactEnvName = flagValue(args, "--artifact-env");
|
|
415
|
+
const artifactSources = [rawArtifact, artifactEnvName].filter(value => value !== undefined);
|
|
416
|
+
if (artifactSources.length === 0 || artifactSources.some(value => value === "")) {
|
|
417
|
+
throw new RalplanCommandError(2, "--artifact or --artifact-env is required for ralplan --write");
|
|
418
|
+
}
|
|
419
|
+
if (artifactSources.length > 1) {
|
|
420
|
+
throw new RalplanCommandError(2, "--artifact and --artifact-env are mutually exclusive");
|
|
421
|
+
}
|
|
422
|
+
if (artifactEnvName !== undefined && artifactEnvName !== SKC_RALPLAN_ARTIFACT_ENV) {
|
|
423
|
+
throw new RalplanCommandError(2, `--artifact-env must be ${SKC_RALPLAN_ARTIFACT_ENV}`);
|
|
414
424
|
}
|
|
415
425
|
|
|
416
426
|
const session = resolveSkcSessionForWrite(cwd, {
|
|
@@ -430,7 +440,13 @@ async function resolveArtifactArgs(args: readonly string[], cwd: string): Promis
|
|
|
430
440
|
const runId = explicitRunId || (await readActiveRunId(cwd, sessionId)) || sessionIdRaw || defaultRunId();
|
|
431
441
|
assertSafePathComponent(runId, "run-id");
|
|
432
442
|
|
|
433
|
-
const artifact =
|
|
443
|
+
const artifact =
|
|
444
|
+
artifactEnvName !== undefined
|
|
445
|
+
? (process.env[SKC_RALPLAN_ARTIFACT_ENV] ?? "")
|
|
446
|
+
: await resolveArtifactContent(rawArtifact!, cwd);
|
|
447
|
+
if (artifact === "") {
|
|
448
|
+
throw new RalplanCommandError(2, "artifact content is empty");
|
|
449
|
+
}
|
|
434
450
|
return { stage: stage as RalplanStage, stageN, runId, artifact, sessionId, json: hasFlag(args, "--json") };
|
|
435
451
|
}
|
|
436
452
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const SKC_RESTRICTED_ROLE_AGENT_BASH_ENV = "SKC_RESTRICTED_ROLE_AGENT_BASH";
|
|
2
|
+
export const SKC_RALPLAN_ARTIFACT_ENV = "SKC_RALPLAN_ARTIFACT";
|
|
2
3
|
|
|
3
4
|
export function isRestrictedRoleAgentBash(): boolean {
|
|
4
5
|
return process.env[SKC_RESTRICTED_ROLE_AGENT_BASH_ENV] === "1";
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
import * as fsSync from "node:fs";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import type { AssistantMessage } from "@sayknow-cli/ai";
|
|
4
|
-
import { logger } from "@sayknow-cli/utils";
|
|
5
|
+
import { logger, postmortem } from "@sayknow-cli/utils";
|
|
5
6
|
|
|
6
7
|
export const SKC_COORDINATOR_SESSION_STATE_FILE_ENV = "SKC_COORDINATOR_SESSION_STATE_FILE";
|
|
7
8
|
export const SKC_COORDINATOR_SESSION_ID_ENV = "SKC_COORDINATOR_SESSION_ID";
|
|
9
|
+
export const SKC_COORDINATOR_SESSION_BRANCH_ENV = "SKC_COORDINATOR_SESSION_BRANCH";
|
|
8
10
|
|
|
9
11
|
export type RuntimeState = "ready_for_input" | "running" | "needs_user_input" | "completed" | "errored";
|
|
10
12
|
|
|
13
|
+
type FinalResponseSource = "agent_end" | "launch_error";
|
|
14
|
+
const MAX_PUBLIC_ERROR_MESSAGE_LENGTH = 2000;
|
|
15
|
+
|
|
11
16
|
interface RuntimeStateEvent {
|
|
12
17
|
type: string;
|
|
13
18
|
messages?: unknown[];
|
|
@@ -17,6 +22,7 @@ interface RuntimeStateContext {
|
|
|
17
22
|
sessionId: string;
|
|
18
23
|
cwd: string;
|
|
19
24
|
sessionFile?: string | null;
|
|
25
|
+
branch?: string | null;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
interface RuntimeStateSidecarPayload {
|
|
@@ -26,6 +32,7 @@ interface RuntimeStateSidecarPayload {
|
|
|
26
32
|
ready_for_input?: unknown;
|
|
27
33
|
cwd?: unknown;
|
|
28
34
|
session_file?: unknown;
|
|
35
|
+
final_response?: { source?: unknown };
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export type TerminalRuntimeStateStatus =
|
|
@@ -103,7 +110,7 @@ function assistantText(assistant: AssistantMessage | undefined): string | null {
|
|
|
103
110
|
function finalResponseForEvent(event: RuntimeStateEvent): {
|
|
104
111
|
text: string | null;
|
|
105
112
|
format: "markdown";
|
|
106
|
-
source:
|
|
113
|
+
source: FinalResponseSource;
|
|
107
114
|
artifact_path: null;
|
|
108
115
|
truncated: false;
|
|
109
116
|
} | null {
|
|
@@ -127,6 +134,118 @@ function stateForEvent(event: RuntimeStateEvent): RuntimeState | null {
|
|
|
127
134
|
return null;
|
|
128
135
|
}
|
|
129
136
|
|
|
137
|
+
function readPreviousPayload(stateFile: string): Record<string, unknown> {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(fsSync.readFileSync(stateFile, "utf8")) as Record<string, unknown>;
|
|
140
|
+
} catch {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function shouldPreserveTerminalPayload(previous: RuntimeStateSidecarPayload): boolean {
|
|
146
|
+
if (previous.state !== "completed" && previous.state !== "errored") return false;
|
|
147
|
+
const source = previous.final_response?.source;
|
|
148
|
+
return source === "agent_end" || source === "launch_error";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function branchForContext(context: RuntimeStateContext): string | null {
|
|
152
|
+
return context.branch ?? (process.env[SKC_COORDINATOR_SESSION_BRANCH_ENV]?.trim() || null);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function basePayload(input: {
|
|
156
|
+
context: RuntimeStateContext;
|
|
157
|
+
previous: Record<string, unknown>;
|
|
158
|
+
state: RuntimeState;
|
|
159
|
+
now: string;
|
|
160
|
+
source: string;
|
|
161
|
+
event: string;
|
|
162
|
+
reason: string | null;
|
|
163
|
+
}): Record<string, unknown> {
|
|
164
|
+
return {
|
|
165
|
+
schema_version: 1,
|
|
166
|
+
session_id: process.env[SKC_COORDINATOR_SESSION_ID_ENV]?.trim() || input.context.sessionId,
|
|
167
|
+
state: input.state,
|
|
168
|
+
ready_for_input: input.state === "completed" || input.state === "ready_for_input",
|
|
169
|
+
updated_at: input.now,
|
|
170
|
+
current_turn_id: typeof input.previous.current_turn_id === "string" ? input.previous.current_turn_id : null,
|
|
171
|
+
last_turn_id: typeof input.previous.last_turn_id === "string" ? input.previous.last_turn_id : null,
|
|
172
|
+
live: input.state === "running",
|
|
173
|
+
reason: input.reason,
|
|
174
|
+
source: input.source,
|
|
175
|
+
event: input.event,
|
|
176
|
+
cwd: input.context.cwd,
|
|
177
|
+
workdir: input.context.cwd,
|
|
178
|
+
branch: branchForContext(input.context),
|
|
179
|
+
session_file: input.context.sessionFile ?? null,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function publicSafeErrorMessage(message: string): string {
|
|
183
|
+
const normalized = message.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
184
|
+
if (normalized.length <= MAX_PUBLIC_ERROR_MESSAGE_LENGTH) return normalized;
|
|
185
|
+
return `${normalized.slice(0, MAX_PUBLIC_ERROR_MESSAGE_LENGTH)}…`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function errorMessageForPostmortem(reason: postmortem.Reason): string {
|
|
189
|
+
return publicSafeErrorMessage(`SKC process cleanup ran for ${reason}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function numericProcessExitCode(defaultCode: number | null): number | null {
|
|
193
|
+
return typeof process.exitCode === "number" ? process.exitCode : defaultCode;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function postmortemExitDetails(reason: postmortem.Reason): {
|
|
197
|
+
state: RuntimeState;
|
|
198
|
+
reason: string;
|
|
199
|
+
exitKind: string;
|
|
200
|
+
exitCode: number | null;
|
|
201
|
+
signal: string | null;
|
|
202
|
+
error?: { code: string; message: string; recoverable: true };
|
|
203
|
+
} {
|
|
204
|
+
if (reason === postmortem.Reason.EXIT || reason === postmortem.Reason.MANUAL) {
|
|
205
|
+
const exitCode = numericProcessExitCode(0) ?? 0;
|
|
206
|
+
const state: RuntimeState = exitCode === 0 ? "completed" : "errored";
|
|
207
|
+
return {
|
|
208
|
+
state,
|
|
209
|
+
reason: reason === postmortem.Reason.EXIT ? "process_exit" : "manual_cleanup",
|
|
210
|
+
exitKind: reason,
|
|
211
|
+
exitCode,
|
|
212
|
+
signal: null,
|
|
213
|
+
...(state === "errored"
|
|
214
|
+
? {
|
|
215
|
+
error: {
|
|
216
|
+
code: "process_exit",
|
|
217
|
+
message: publicSafeErrorMessage(`SKC process exited with code ${exitCode}`),
|
|
218
|
+
recoverable: true,
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
: {}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
const signalByReason: Partial<Record<postmortem.Reason, string>> = {
|
|
225
|
+
[postmortem.Reason.SIGINT]: "SIGINT",
|
|
226
|
+
[postmortem.Reason.SIGTERM]: "SIGTERM",
|
|
227
|
+
[postmortem.Reason.SIGHUP]: "SIGHUP",
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
state: "errored",
|
|
231
|
+
reason,
|
|
232
|
+
exitKind: reason,
|
|
233
|
+
exitCode: numericProcessExitCode(null),
|
|
234
|
+
signal: signalByReason[reason] ?? null,
|
|
235
|
+
error: { code: reason, message: errorMessageForPostmortem(reason), recoverable: true },
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function writeStateFileSync(stateFile: string, payload: Record<string, unknown>): void {
|
|
240
|
+
fsSync.mkdirSync(path.dirname(stateFile), { recursive: true });
|
|
241
|
+
fsSync.writeFileSync(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function writeStateFile(stateFile: string, payload: Record<string, unknown>): Promise<void> {
|
|
245
|
+
await fs.mkdir(path.dirname(stateFile), { recursive: true });
|
|
246
|
+
await Bun.write(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
|
247
|
+
}
|
|
248
|
+
|
|
130
249
|
export async function persistCoordinatorRuntimeStateFromEvent(
|
|
131
250
|
event: RuntimeStateEvent,
|
|
132
251
|
context: RuntimeStateContext,
|
|
@@ -136,42 +255,66 @@ export async function persistCoordinatorRuntimeStateFromEvent(
|
|
|
136
255
|
const state = stateForEvent(event);
|
|
137
256
|
if (!state) return;
|
|
138
257
|
const now = new Date().toISOString();
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
previous = JSON.parse(await Bun.file(stateFile).text()) as Record<string, unknown>;
|
|
142
|
-
} catch {
|
|
143
|
-
previous = {};
|
|
144
|
-
}
|
|
258
|
+
const previous = readPreviousPayload(stateFile);
|
|
145
259
|
const finalResponse = finalResponseForEvent(event);
|
|
146
260
|
const payload = {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
state,
|
|
150
|
-
ready_for_input: state === "completed" || state === "ready_for_input",
|
|
151
|
-
updated_at: now,
|
|
152
|
-
current_turn_id: typeof previous.current_turn_id === "string" ? previous.current_turn_id : null,
|
|
153
|
-
last_turn_id: typeof previous.last_turn_id === "string" ? previous.last_turn_id : null,
|
|
154
|
-
live: typeof previous.live === "boolean" ? previous.live : null,
|
|
155
|
-
reason: null,
|
|
156
|
-
source: "agent_session_event",
|
|
157
|
-
event: event.type,
|
|
158
|
-
cwd: context.cwd,
|
|
159
|
-
session_file: context.sessionFile ?? null,
|
|
261
|
+
...basePayload({ context, previous, state, now, source: "agent_session_event", event: event.type, reason: null }),
|
|
262
|
+
...(state === "completed" || state === "errored" ? { ended_at: now } : {}),
|
|
160
263
|
...(finalResponse ? { final_response: finalResponse } : {}),
|
|
161
264
|
...(state === "errored"
|
|
162
265
|
? {
|
|
163
266
|
error: {
|
|
164
267
|
code: "agent_error",
|
|
165
|
-
message: lastAssistant(event.messages)?.errorMessage ?? "agent_error",
|
|
268
|
+
message: publicSafeErrorMessage(lastAssistant(event.messages)?.errorMessage ?? "agent_error"),
|
|
166
269
|
recoverable: true,
|
|
167
270
|
},
|
|
168
271
|
}
|
|
169
272
|
: {}),
|
|
170
273
|
};
|
|
171
274
|
try {
|
|
172
|
-
await
|
|
173
|
-
await Bun.write(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
|
275
|
+
await writeStateFile(stateFile, payload);
|
|
174
276
|
} catch (error) {
|
|
175
277
|
logger.warn("Failed to persist coordinator runtime state", { error: String(error), stateFile });
|
|
176
278
|
}
|
|
177
279
|
}
|
|
280
|
+
|
|
281
|
+
export function persistCoordinatorRuntimeStateFromPostmortem(
|
|
282
|
+
reason: postmortem.Reason,
|
|
283
|
+
context: RuntimeStateContext,
|
|
284
|
+
): void {
|
|
285
|
+
const stateFile = process.env[SKC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim();
|
|
286
|
+
if (!stateFile) return;
|
|
287
|
+
const previous = readPreviousPayload(stateFile);
|
|
288
|
+
if (shouldPreserveTerminalPayload(previous as RuntimeStateSidecarPayload)) return;
|
|
289
|
+
const now = new Date().toISOString();
|
|
290
|
+
const details = postmortemExitDetails(reason);
|
|
291
|
+
const payload = {
|
|
292
|
+
...basePayload({
|
|
293
|
+
context,
|
|
294
|
+
previous,
|
|
295
|
+
state: details.state,
|
|
296
|
+
now,
|
|
297
|
+
source: "process_postmortem",
|
|
298
|
+
event: "process_exit",
|
|
299
|
+
reason: details.reason,
|
|
300
|
+
}),
|
|
301
|
+
ended_at: now,
|
|
302
|
+
detected_at: now,
|
|
303
|
+
exit_kind: details.exitKind,
|
|
304
|
+
exit_code: details.exitCode,
|
|
305
|
+
signal: details.signal,
|
|
306
|
+
...(details.error ? { error: details.error } : {}),
|
|
307
|
+
};
|
|
308
|
+
try {
|
|
309
|
+
writeStateFileSync(stateFile, payload);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
logger.warn("Failed to persist coordinator runtime state during postmortem", { error: String(error), stateFile });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function registerCoordinatorRuntimeStateFinalizer(context: RuntimeStateContext): () => void {
|
|
316
|
+
if (!process.env[SKC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim()) return () => {};
|
|
317
|
+
return postmortem.register("coordinator-runtime-state", reason => {
|
|
318
|
+
persistCoordinatorRuntimeStateFromPostmortem(reason, context);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
@@ -847,11 +847,14 @@ async function writeJsonAtomic(
|
|
|
847
847
|
forced: options?.force ?? false,
|
|
848
848
|
},
|
|
849
849
|
});
|
|
850
|
-
// `writeResult.revision`
|
|
851
|
-
// write actually owns.
|
|
852
|
-
// could
|
|
853
|
-
//
|
|
854
|
-
|
|
850
|
+
// `writeResult.stamped` and `.revision` are computed inside the writer lock, so they are
|
|
851
|
+
// the envelope/revision this write actually owns. Never post-lock re-read here: a concurrent
|
|
852
|
+
// writer could advance the file before that read and make this payload publish another
|
|
853
|
+
// writer's newer revision into the derived active-state cache.
|
|
854
|
+
if (!writeResult.written || !isPlainObject(writeResult.stamped)) {
|
|
855
|
+
throw new Error(`state writer did not return a stamped workflow envelope for ${filePath}`);
|
|
856
|
+
}
|
|
857
|
+
return { warning, stamped: writeResult.stamped, revision: writeResult.revision };
|
|
855
858
|
}
|
|
856
859
|
|
|
857
860
|
function parseFieldsFlag(args: readonly string[]): StateProjectionField[] | undefined {
|
|
@@ -99,7 +99,7 @@ export interface GuardedStateWriterOptions extends StateWriterOptions {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export type GuardedWriteResult =
|
|
102
|
-
| { path: string; written: true; revision: number }
|
|
102
|
+
| { path: string; written: true; revision: number; stamped: unknown }
|
|
103
103
|
| { path: string; written: false; reason: "stale-skip"; revision: number };
|
|
104
104
|
|
|
105
105
|
export interface StateWriterOptions {
|
|
@@ -511,7 +511,7 @@ async function writeGuardedResolvedJsonAtomic(
|
|
|
511
511
|
const next = stampStateRevision(withWorkflowReceipt(value, buildReceipt(options)), currentRevision + 1);
|
|
512
512
|
await atomicWrite(filePath, jsonText(next));
|
|
513
513
|
await maybeAudit(filePath, options);
|
|
514
|
-
return { path: filePath, written: true, revision: currentRevision + 1 };
|
|
514
|
+
return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
const incomingSourceRevision =
|
|
@@ -526,7 +526,7 @@ async function writeGuardedResolvedJsonAtomic(
|
|
|
526
526
|
);
|
|
527
527
|
await atomicWrite(filePath, jsonText(next));
|
|
528
528
|
await maybeAudit(filePath, options);
|
|
529
|
-
return { path: filePath, written: true, revision: currentRevision + 1 };
|
|
529
|
+
return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
|
|
530
530
|
},
|
|
531
531
|
options.lock,
|
|
532
532
|
);
|
|
@@ -574,7 +574,7 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
|
|
|
574
574
|
}
|
|
575
575
|
await atomicWrite(filePath, jsonText(next));
|
|
576
576
|
await maybeAudit(filePath, options);
|
|
577
|
-
return { path: filePath, written: true, revision: currentRevision + 1 };
|
|
577
|
+
return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
const incomingSourceRevision =
|
|
@@ -599,7 +599,7 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
|
|
|
599
599
|
}
|
|
600
600
|
await atomicWrite(filePath, jsonText(next));
|
|
601
601
|
await maybeAudit(filePath, options);
|
|
602
|
-
return { path: filePath, written: true, revision: currentRevision + 1 };
|
|
602
|
+
return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
|
|
603
603
|
},
|
|
604
604
|
options.lock,
|
|
605
605
|
);
|
|
@@ -1896,13 +1896,27 @@ function readCurrentTmuxLeaderContext(tmuxCommand: string, env: NodeJS.ProcessEn
|
|
|
1896
1896
|
}
|
|
1897
1897
|
return { sessionName, windowIndex, leaderPaneId, target: `${sessionName}:${windowIndex}` };
|
|
1898
1898
|
}
|
|
1899
|
-
export function resolveSkcWorkerCommand(
|
|
1899
|
+
export function resolveSkcWorkerCommand(
|
|
1900
|
+
cwd = process.cwd(),
|
|
1901
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
1902
|
+
platform: NodeJS.Platform = process.platform,
|
|
1903
|
+
argv: string[] = process.argv,
|
|
1904
|
+
execPath = process.execPath,
|
|
1905
|
+
): string {
|
|
1900
1906
|
const explicit = env.SKC_TEAM_WORKER_COMMAND?.trim();
|
|
1901
1907
|
if (explicit) return explicit;
|
|
1902
|
-
const entrypoint =
|
|
1903
|
-
if (entrypoint
|
|
1904
|
-
|
|
1905
|
-
|
|
1908
|
+
const entrypoint = argv[1];
|
|
1909
|
+
if (!entrypoint) return "skc";
|
|
1910
|
+
const pathModule = platform === "win32" ? path.win32 : path;
|
|
1911
|
+
const resolvedEntrypoint = pathModule.isAbsolute(entrypoint) ? entrypoint : pathModule.resolve(cwd, entrypoint);
|
|
1912
|
+
if (platform === "win32") {
|
|
1913
|
+
if (entrypoint.endsWith(".ts") || entrypoint.endsWith(".js") || entrypoint.endsWith(".mjs"))
|
|
1914
|
+
return `${powershellQuote(execPath)} ${powershellQuote(resolvedEntrypoint)}`;
|
|
1915
|
+
if (pathModule.basename(entrypoint).startsWith("skc")) return powershellQuote(resolvedEntrypoint);
|
|
1916
|
+
return "skc";
|
|
1917
|
+
}
|
|
1918
|
+
if (entrypoint.endsWith(".ts")) return `${shellQuote(execPath)} ${shellQuote(resolvedEntrypoint)}`;
|
|
1919
|
+
if (path.basename(entrypoint).startsWith("skc")) return shellQuote(path.resolve(cwd, entrypoint));
|
|
1906
1920
|
return "skc";
|
|
1907
1921
|
}
|
|
1908
1922
|
/** @internal Exported for unit tests. */
|
|
@@ -1937,7 +1951,8 @@ export function buildWorkerCommand(
|
|
|
1937
1951
|
...(worker.worktree_path ? [envAssignment("SKC_TEAM_WORKTREE_PATH", worker.worktree_path)] : []),
|
|
1938
1952
|
];
|
|
1939
1953
|
const joined = platform === "win32" ? envLines.join(" ") : envLines.join(" ");
|
|
1940
|
-
|
|
1954
|
+
const invocation = platform === "win32" ? `& ${config.worker_command}` : config.worker_command;
|
|
1955
|
+
return `${joined} ${invocation} ${quote(prompt)}`;
|
|
1941
1956
|
}
|
|
1942
1957
|
interface SkcTeamInitialLane {
|
|
1943
1958
|
label: string;
|
|
@@ -86,6 +86,22 @@ export function buildSkcTmuxExactOptionTarget(
|
|
|
86
86
|
return `=${sessionName}:`;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Build the exact-session target for tmux *session-scoped* commands such as
|
|
91
|
+
* `attach-session` and `kill-session`. Native tmux accepts `=NAME` for an
|
|
92
|
+
* exact session match, but Windows psmux 3.3.x rejects that target form for
|
|
93
|
+
* session commands even though the bare `NAME` resolves. Keep native tmux on
|
|
94
|
+
* exact targets and intentionally use the bare session name for psmux.
|
|
95
|
+
*/
|
|
96
|
+
export function buildSkcTmuxExactSessionTarget(
|
|
97
|
+
sessionName: string,
|
|
98
|
+
opts: { env?: NodeJS.ProcessEnv; platform?: NodeJS.Platform; binary?: ResolvedTmuxBinary } = {},
|
|
99
|
+
): string {
|
|
100
|
+
const binary = opts.binary ?? resolveSkcTmuxBinary({ env: opts.env, platform: opts.platform });
|
|
101
|
+
if (binary.isPsmux) return sessionName;
|
|
102
|
+
return `=${sessionName}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
89
105
|
export const SKC_TMUX_UNTAGGED_REASON = "skc_tmux_session_untagged";
|
|
90
106
|
|
|
91
107
|
export function buildSkcTmuxUntaggedSessionHint(tmuxCommand: string): string {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveSkcTmuxBinary } from "./psmux-detect";
|
|
2
2
|
import {
|
|
3
3
|
buildSkcTmuxExactOptionTarget,
|
|
4
|
+
buildSkcTmuxExactSessionTarget,
|
|
4
5
|
buildSkcTmuxProfileCommands,
|
|
5
6
|
buildSkcTmuxSessionName,
|
|
6
7
|
buildSkcTmuxUntaggedSessionError,
|
|
@@ -60,7 +61,7 @@ function runTmux(args: string[], env: NodeJS.ProcessEnv = process.env): string {
|
|
|
60
61
|
|
|
61
62
|
function tryKillSession(sessionName: string, env: NodeJS.ProcessEnv): void {
|
|
62
63
|
try {
|
|
63
|
-
runTmux(["kill-session", "-t",
|
|
64
|
+
runTmux(["kill-session", "-t", buildSkcTmuxExactSessionTarget(sessionName, { env })], env);
|
|
64
65
|
} catch {
|
|
65
66
|
// Best-effort cleanup only; preserve the original create/tag failure.
|
|
66
67
|
}
|
|
@@ -360,7 +361,7 @@ export function removeSkcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv
|
|
|
360
361
|
if (readProfileForExactTarget(session.name, env) !== SKC_TMUX_PROFILE_VALUE) {
|
|
361
362
|
throw new Error(`skc_tmux_session_not_managed:${sessionName}`);
|
|
362
363
|
}
|
|
363
|
-
runTmux(["kill-session", "-t",
|
|
364
|
+
runTmux(["kill-session", "-t", buildSkcTmuxExactSessionTarget(session.name, { env })], env);
|
|
364
365
|
return session;
|
|
365
366
|
}
|
|
366
367
|
|
|
@@ -404,18 +405,21 @@ export function forceCloseSkcTmuxSession(
|
|
|
404
405
|
}
|
|
405
406
|
}
|
|
406
407
|
// Intentionally NOT refusing live/attached panes — force-close is hard-kill.
|
|
407
|
-
runTmux(["kill-session", "-t",
|
|
408
|
+
runTmux(["kill-session", "-t", buildSkcTmuxExactSessionTarget(session.name, { env })], env);
|
|
408
409
|
return session;
|
|
409
410
|
}
|
|
410
411
|
|
|
411
412
|
export function attachSkcTmuxSession(sessionName: string, env: NodeJS.ProcessEnv = process.env): never {
|
|
412
413
|
const session = statusSkcTmuxSession(sessionName, env);
|
|
413
414
|
const tmuxCommand = resolveSkcTmuxCommand(env);
|
|
414
|
-
const result = Bun.spawnSync(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
415
|
+
const result = Bun.spawnSync(
|
|
416
|
+
[tmuxCommand, "attach-session", "-t", buildSkcTmuxExactSessionTarget(session.name, { env })],
|
|
417
|
+
{
|
|
418
|
+
stdin: "inherit",
|
|
419
|
+
stdout: "inherit",
|
|
420
|
+
stderr: "inherit",
|
|
421
|
+
env,
|
|
422
|
+
},
|
|
423
|
+
);
|
|
420
424
|
process.exit(result.exitCode ?? 1);
|
|
421
425
|
}
|