@sayknow-cli/coding-agent 0.5.1 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +185 -14
- package/dist/types/cli/update-cli.d.ts +8 -1
- package/dist/types/commands/session.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +20 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-registry.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/telegram-autostart.d.ts +9 -1
- package/dist/types/dap/client.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +5 -0
- package/dist/types/modes/components/pet-capability.d.ts +8 -7
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +13 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +7 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -0
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/runtime-mcp/transports/stdio.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +3 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +9 -0
- package/dist/types/session/session-storage.d.ts +17 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +22 -0
- package/dist/types/session-import/command.d.ts +20 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +28 -0
- package/dist/types/session-import/types.d.ts +125 -0
- package/dist/types/skc-runtime/boot-generation.d.ts +59 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +10 -2
- package/dist/types/skc-runtime/launch-worktree.d.ts +17 -1
- package/dist/types/skc-runtime/session-restore-runtime.d.ts +41 -0
- package/dist/types/skc-runtime/session-restore.d.ts +99 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +160 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +26 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +4 -0
- package/dist/types/slash-commands/types.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +169 -4
- package/package.json +16 -7
- package/scripts/generate-sdk-operation-inventory.ts +5 -6
- package/src/cli/config-cli.ts +23 -8
- package/src/cli/update-cli.ts +265 -21
- package/src/commands/session.ts +88 -2
- package/src/config/atomic-yaml-patch.ts +167 -30
- package/src/config/keybindings.ts +10 -0
- package/src/config/model-profiles.ts +69 -1
- package/src/config/model-registry.ts +72 -38
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +9 -0
- package/src/config/settings.ts +13 -2
- package/src/config/telegram-autostart.ts +11 -4
- package/src/dap/client.ts +78 -30
- package/src/defaults/skc/skills/deep-interview/SKILL.md +29 -3
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/lsp/client.ts +17 -29
- package/src/main.ts +1 -1
- package/src/modes/action-registry.ts +2 -0
- package/src/modes/components/pet-capability.ts +22 -13
- package/src/modes/components/pet-selector.ts +1 -1
- package/src/modes/components/sayknow-pet-widget.ts +41 -7
- package/src/modes/components/welcome.ts +5 -0
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/goal-mode-controller.ts +7 -2
- package/src/modes/controllers/input-controller.ts +71 -6
- package/src/modes/controllers/plan-mode-controller.ts +8 -1
- package/src/modes/controllers/runtime-mcp-command-controller.ts +14 -0
- package/src/modes/controllers/selector-controller.ts +47 -19
- package/src/modes/interactive-mode.ts +54 -4
- package/src/modes/shared/agent-wire/unattended-session.ts +40 -9
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -0
- package/src/modes/types.ts +5 -1
- package/src/notifications/lifecycle-control-runtime.ts +258 -179
- package/src/prompts/system/eager-todo.md +2 -0
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/system-prompt.md +4 -2
- package/src/runtime-mcp/transports/stdio.ts +96 -33
- package/src/sdk/broker/lifecycle.ts +6 -5
- package/src/sdk/bus/lifecycle-control-runtime.ts +189 -110
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +25 -18
- package/src/session/agent-session.ts +120 -25
- package/src/session/internal/managed-session-scope.ts +41 -6
- package/src/session/internal/managed-session-storage.ts +14 -0
- package/src/session/session-manager.ts +113 -11
- package/src/session/session-storage.ts +70 -0
- package/src/session-import/claude.ts +382 -0
- package/src/session-import/codex.ts +458 -0
- package/src/session-import/command.ts +61 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +27 -0
- package/src/session-import/redact.ts +125 -0
- package/src/session-import/service.ts +749 -0
- package/src/session-import/types.ts +163 -0
- package/src/skc-runtime/boot-generation.ts +172 -0
- package/src/skc-runtime/launch-tmux.ts +219 -41
- package/src/skc-runtime/launch-worktree.ts +277 -4
- package/src/skc-runtime/session-restore-runtime.ts +120 -0
- package/src/skc-runtime/session-restore.ts +296 -0
- package/src/skc-runtime/session-state-sidecar.ts +41 -0
- package/src/skc-runtime/tmux-owner-isolation.ts +665 -0
- package/src/skc-runtime/tmux-sessions.ts +284 -108
- package/src/slash-commands/acp-builtins.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +109 -5
- package/src/slash-commands/types.ts +5 -0
- package/src/tools/ask.ts +188 -10
- package/src/tools/eval.ts +2 -2
- package/src/export/html/template.css +0 -1060
- package/src/export/html/template.html +0 -47
- package/src/export/html/template.js +0 -2348
- package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
- package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
- package/vendor/insane-search/engine/tests/test_u1.py +0 -200
- package/vendor/insane-search/engine/tests/test_u4.py +0 -131
- package/vendor/insane-search/engine/tests/test_u5.py +0 -163
- package/vendor/insane-search/engine/tests/test_u7.py +0 -124
- package/vendor/insane-search/engine/tests/test_u8.py +0 -216
package/src/config/settings.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { AgentStorage } from "../session/agent-storage";
|
|
|
39
39
|
import { type EditMode, normalizeEditMode } from "../utils/edit-mode";
|
|
40
40
|
import {
|
|
41
41
|
type AtomicYamlPatch,
|
|
42
|
+
AtomicYamlRetargetError,
|
|
42
43
|
applyAtomicYamlPatches,
|
|
43
44
|
applyAtomicYamlPatchesWithCurrent,
|
|
44
45
|
atomicYamlPathHash,
|
|
@@ -830,22 +831,28 @@ export class Settings implements NotificationSettingsReader {
|
|
|
830
831
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
|
|
831
832
|
this.#releasePendingSaveSlot();
|
|
832
833
|
const observedSave = this.#savePromise;
|
|
834
|
+
let saveError: unknown;
|
|
833
835
|
try {
|
|
834
836
|
await observedSave;
|
|
835
|
-
} catch {
|
|
837
|
+
} catch (error) {
|
|
836
838
|
// Historical flush() behavior logs background failures but does not reject.
|
|
839
|
+
saveError = error;
|
|
837
840
|
}
|
|
841
|
+
if (saveError instanceof AtomicYamlRetargetError) return;
|
|
838
842
|
// A failed predecessor may settle just before a new mutation observes its
|
|
839
843
|
// still-reserved slot. Explicit flush owns one fresh attempt for remaining
|
|
840
844
|
// dirty patches instead of leaving them stranded or retrying forever.
|
|
841
845
|
if (this.#modified.size > 0 && this.#savePromise === observedSave) {
|
|
842
846
|
if (!this.#pendingSaveSlot) this.#queueSave();
|
|
843
847
|
this.#releasePendingSaveSlot();
|
|
848
|
+
let retryError: unknown;
|
|
844
849
|
try {
|
|
845
850
|
await this.#savePromise;
|
|
846
|
-
} catch {
|
|
851
|
+
} catch (error) {
|
|
847
852
|
// Keep dirty state for a later explicit flush or mutation.
|
|
853
|
+
retryError = error;
|
|
848
854
|
}
|
|
855
|
+
if (retryError instanceof AtomicYamlRetargetError) return;
|
|
849
856
|
}
|
|
850
857
|
await this.#refreshDurableSettings();
|
|
851
858
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
|
|
@@ -871,6 +878,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
871
878
|
saveError = error;
|
|
872
879
|
}
|
|
873
880
|
await this.#refreshDurableSettings();
|
|
881
|
+
if (saveError instanceof AtomicYamlRetargetError) throw saveError;
|
|
874
882
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
|
|
875
883
|
this.#queueSave();
|
|
876
884
|
this.#releasePendingSaveSlot();
|
|
@@ -1721,6 +1729,9 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1721
1729
|
logger.warn("Settings: refresh after background save failure failed", { error: String(refreshError) });
|
|
1722
1730
|
}
|
|
1723
1731
|
throw error;
|
|
1732
|
+
})
|
|
1733
|
+
.finally(() => {
|
|
1734
|
+
if (this.#pendingSaveSlot === slot) this.#pendingSaveSlot = undefined;
|
|
1724
1735
|
});
|
|
1725
1736
|
this.#savePromise = save;
|
|
1726
1737
|
void save.catch(() => {});
|
|
@@ -10,11 +10,18 @@
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { getConfigRootDir, isCompiledBinary, logger } from "@sayknow-cli/utils";
|
|
13
|
-
import { settings } from "./settings";
|
|
13
|
+
import { settings as globalSettings, type Settings } from "./settings";
|
|
14
14
|
import type { TelegramSettings } from "./settings-schema";
|
|
15
15
|
import { telegramSettingsToEnv, validateTelegramSettings } from "./telegram-env-bridge";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The startup command runs against an explicit Settings instance (tests and
|
|
19
|
+
* embedders inject one), so read through it instead of the global proxy —
|
|
20
|
+
* which may not be initialized at all on those paths.
|
|
21
|
+
*/
|
|
22
|
+
type TelegramSettingsSource = Pick<Settings, "get">;
|
|
23
|
+
|
|
24
|
+
function readTelegramSettings(settings: TelegramSettingsSource): TelegramSettings {
|
|
18
25
|
return {
|
|
19
26
|
enabled: settings.get("telegram.enabled"),
|
|
20
27
|
botToken: settings.get("telegram.botToken"),
|
|
@@ -69,10 +76,10 @@ function isAlreadyRunning(pidFile: string): boolean {
|
|
|
69
76
|
* it in the background. Never throws — failures are logged as warnings so the
|
|
70
77
|
* main skc session is unaffected.
|
|
71
78
|
*/
|
|
72
|
-
export async function maybeAutostartTelegramRemote(): Promise<void> {
|
|
79
|
+
export async function maybeAutostartTelegramRemote(settings: TelegramSettingsSource = globalSettings): Promise<void> {
|
|
73
80
|
if (!settings.get("telegram.enabled")) return;
|
|
74
81
|
|
|
75
|
-
const config = readTelegramSettings();
|
|
82
|
+
const config = readTelegramSettings(settings);
|
|
76
83
|
const errors = validateTelegramSettings(config);
|
|
77
84
|
if (errors.length > 0) {
|
|
78
85
|
logger.warn("Telegram Remote enabled but misconfigured, skipping autostart", { errors });
|
package/src/dap/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
-
import { logger } from "@sayknow-cli/utils";
|
|
3
|
+
import { isKnownSinkPeerClosedError, logger } from "@sayknow-cli/utils";
|
|
4
4
|
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
5
5
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
6
6
|
import { type OwnedProcess, spawnOwnedProcess } from "../runtime/process-lifecycle";
|
|
@@ -62,9 +62,9 @@ function parseMessage(
|
|
|
62
62
|
|
|
63
63
|
async function writeMessage(sink: DapWriteSink, message: DapRequestMessage | DapResponseMessage): Promise<void> {
|
|
64
64
|
const content = JSON.stringify(message);
|
|
65
|
-
|
|
66
|
-
sink.write(
|
|
67
|
-
await sink.flush();
|
|
65
|
+
const frame = `Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n${content}`;
|
|
66
|
+
await Promise.resolve(sink.write(frame));
|
|
67
|
+
await Promise.resolve(sink.flush());
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function toErrorMessage(value: unknown): string {
|
|
@@ -98,6 +98,9 @@ export class DapClient {
|
|
|
98
98
|
#messageBuffer = Buffer.alloc(0);
|
|
99
99
|
#isReading = false;
|
|
100
100
|
#disposed = false;
|
|
101
|
+
#terminalError: Error | undefined;
|
|
102
|
+
#disposePromise: Promise<void> | undefined;
|
|
103
|
+
#writeQueue: Promise<void> = Promise.resolve();
|
|
101
104
|
#lastActivity = Date.now();
|
|
102
105
|
#capabilities?: DapCapabilities;
|
|
103
106
|
#eventHandlers = new Map<string, Set<DapEventHandler>>();
|
|
@@ -119,6 +122,18 @@ export class DapClient {
|
|
|
119
122
|
this.#socket = options?.socket;
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
#queueWriteMessage(message: DapRequestMessage | DapResponseMessage): Promise<void> {
|
|
126
|
+
const write = this.#writeQueue
|
|
127
|
+
.catch(() => {})
|
|
128
|
+
.then(() => {
|
|
129
|
+
if (this.#terminalError) throw this.#terminalError;
|
|
130
|
+
if (this.#disposed) throw new Error(`DAP adapter ${this.adapter.name} is not running`);
|
|
131
|
+
return writeMessage(this.#writeSink, message);
|
|
132
|
+
});
|
|
133
|
+
this.#writeQueue = write.catch(() => {});
|
|
134
|
+
return write;
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
static async spawn({ adapter, cwd }: DapSpawnOptions): Promise<DapClient> {
|
|
123
138
|
if (adapter.connectMode === "socket") {
|
|
124
139
|
return DapClient.#spawnSocket({ adapter, cwd });
|
|
@@ -142,7 +157,7 @@ export class DapClient {
|
|
|
142
157
|
proc.exited.then(() => {
|
|
143
158
|
client.#handleProcessExit();
|
|
144
159
|
});
|
|
145
|
-
void client
|
|
160
|
+
void client.startMessageReader();
|
|
146
161
|
return client;
|
|
147
162
|
}
|
|
148
163
|
|
|
@@ -199,7 +214,7 @@ export class DapClient {
|
|
|
199
214
|
transport = await connectSocket({ unix: socketPath }, 10_000);
|
|
200
215
|
const client = new DapClient(adapter, cwd, owner, transport);
|
|
201
216
|
proc.exited.then(() => client.#handleProcessExit());
|
|
202
|
-
void client
|
|
217
|
+
void client.startMessageReader();
|
|
203
218
|
return client;
|
|
204
219
|
} catch (err) {
|
|
205
220
|
transport?.socket.end();
|
|
@@ -268,7 +283,7 @@ export class DapClient {
|
|
|
268
283
|
const { readable, writeSink, socket } = wrapBunSocket(rawSocket);
|
|
269
284
|
const client = new DapClient(adapter, cwd, owner, { readable, writeSink, socket });
|
|
270
285
|
proc.exited.then(() => client.#handleProcessExit());
|
|
271
|
-
void client
|
|
286
|
+
void client.startMessageReader();
|
|
272
287
|
return client;
|
|
273
288
|
}
|
|
274
289
|
|
|
@@ -364,6 +379,7 @@ export class DapClient {
|
|
|
364
379
|
signal?: AbortSignal,
|
|
365
380
|
timeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
366
381
|
): Promise<TBody> {
|
|
382
|
+
if (this.#terminalError) throw this.#terminalError;
|
|
367
383
|
if (signal?.aborted) {
|
|
368
384
|
throw signal.reason instanceof Error ? signal.reason : new ToolAbortError();
|
|
369
385
|
}
|
|
@@ -412,10 +428,11 @@ export class DapClient {
|
|
|
412
428
|
});
|
|
413
429
|
this.#lastActivity = Date.now();
|
|
414
430
|
try {
|
|
415
|
-
await
|
|
431
|
+
await this.#queueWriteMessage(request);
|
|
416
432
|
} catch (error) {
|
|
417
433
|
this.#pendingRequests.delete(requestSeq);
|
|
418
434
|
cleanup();
|
|
435
|
+
if (isKnownSinkPeerClosedError(error)) throw this.#terminalizeTransport(error);
|
|
419
436
|
throw error;
|
|
420
437
|
}
|
|
421
438
|
return promise;
|
|
@@ -431,37 +448,30 @@ export class DapClient {
|
|
|
431
448
|
...(message ? { message } : {}),
|
|
432
449
|
...(body !== undefined ? { body } : {}),
|
|
433
450
|
};
|
|
434
|
-
await
|
|
451
|
+
await this.#queueWriteMessage(response);
|
|
435
452
|
}
|
|
436
453
|
|
|
437
454
|
async dispose(): Promise<void> {
|
|
438
|
-
if (this.#disposed)
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.#
|
|
443
|
-
} catch {
|
|
444
|
-
/* socket may already be closed */
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
await this.#owner.dispose();
|
|
448
|
-
await this.#owner.awaitExit({ timeoutMs: 1_000 });
|
|
449
|
-
} catch (error) {
|
|
450
|
-
logger.debug("Failed to dispose DAP adapter", {
|
|
451
|
-
adapter: this.adapter.name,
|
|
452
|
-
error: toErrorMessage(error),
|
|
453
|
-
});
|
|
455
|
+
if (!this.#disposed) {
|
|
456
|
+
const error = new Error(`DAP adapter ${this.adapter.name} disposed`);
|
|
457
|
+
this.#disposed = true;
|
|
458
|
+
this.#terminalError = error;
|
|
459
|
+
this.#rejectPendingRequests(error);
|
|
454
460
|
}
|
|
461
|
+
await this.#beginOwnerDisposal();
|
|
455
462
|
}
|
|
456
463
|
|
|
457
|
-
async
|
|
464
|
+
async startMessageReader(): Promise<void> {
|
|
458
465
|
if (this.#isReading) return;
|
|
459
466
|
this.#isReading = true;
|
|
460
467
|
const reader = this.#readable.getReader();
|
|
461
468
|
try {
|
|
462
469
|
while (true) {
|
|
463
470
|
const { done, value } = await reader.read();
|
|
464
|
-
if (done)
|
|
471
|
+
if (done) {
|
|
472
|
+
this.#terminalizeTransport(new Error("DAP readable closed"));
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
465
475
|
const currentBuffer = Buffer.concat([this.#messageBuffer, value]);
|
|
466
476
|
this.#messageBuffer = currentBuffer;
|
|
467
477
|
let workingBuffer = currentBuffer;
|
|
@@ -482,7 +492,7 @@ export class DapClient {
|
|
|
482
492
|
this.#messageBuffer = workingBuffer;
|
|
483
493
|
}
|
|
484
494
|
} catch (error) {
|
|
485
|
-
this.#
|
|
495
|
+
this.#terminalizeTransport(error);
|
|
486
496
|
} finally {
|
|
487
497
|
reader.releaseLock();
|
|
488
498
|
this.#isReading = false;
|
|
@@ -523,9 +533,9 @@ export class DapClient {
|
|
|
523
533
|
try {
|
|
524
534
|
const handler = this.#reverseRequestHandlers.get(message.command);
|
|
525
535
|
if (handler) {
|
|
536
|
+
let body: unknown;
|
|
526
537
|
try {
|
|
527
|
-
|
|
528
|
-
await this.sendResponse(message, true, body);
|
|
538
|
+
body = await handler(message.arguments);
|
|
529
539
|
} catch (error) {
|
|
530
540
|
const errorMessage = toErrorMessage(error);
|
|
531
541
|
await this.sendResponse(
|
|
@@ -539,7 +549,9 @@ export class DapClient {
|
|
|
539
549
|
},
|
|
540
550
|
errorMessage,
|
|
541
551
|
);
|
|
552
|
+
return;
|
|
542
553
|
}
|
|
554
|
+
await this.sendResponse(message, true, body);
|
|
543
555
|
return;
|
|
544
556
|
}
|
|
545
557
|
const errorMessage = `Unsupported DAP request: ${message.command}`;
|
|
@@ -555,6 +567,10 @@ export class DapClient {
|
|
|
555
567
|
errorMessage,
|
|
556
568
|
);
|
|
557
569
|
} catch (error) {
|
|
570
|
+
if (isKnownSinkPeerClosedError(error)) {
|
|
571
|
+
this.#terminalizeTransport(error);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
558
574
|
logger.warn("Failed to answer DAP adapter request", {
|
|
559
575
|
adapter: this.adapter.name,
|
|
560
576
|
command: message.command,
|
|
@@ -586,9 +602,41 @@ export class DapClient {
|
|
|
586
602
|
? `DAP adapter exited (code ${exitCode}): ${stderr}${diagnosticSuffix}`
|
|
587
603
|
: `DAP adapter exited unexpectedly (code ${exitCode})${diagnosticSuffix}`,
|
|
588
604
|
);
|
|
605
|
+
this.#terminalError = error;
|
|
589
606
|
this.#rejectPendingRequests(error);
|
|
590
607
|
}
|
|
591
608
|
|
|
609
|
+
#terminalizeTransport(cause: unknown): Error {
|
|
610
|
+
if (this.#terminalError) return this.#terminalError;
|
|
611
|
+
const error = new Error(`DAP adapter ${this.adapter.name} transport closed`, { cause });
|
|
612
|
+
this.#terminalError = error;
|
|
613
|
+
this.#disposed = true;
|
|
614
|
+
this.#rejectPendingRequests(error);
|
|
615
|
+
void this.#beginOwnerDisposal();
|
|
616
|
+
return error;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#beginOwnerDisposal(): Promise<void> {
|
|
620
|
+
if (this.#disposePromise) return this.#disposePromise;
|
|
621
|
+
this.#disposePromise = (async () => {
|
|
622
|
+
try {
|
|
623
|
+
this.#socket?.end();
|
|
624
|
+
} catch {
|
|
625
|
+
/* socket may already be closed */
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
await this.#owner.dispose();
|
|
629
|
+
await this.#owner.awaitExit({ timeoutMs: 1_000 });
|
|
630
|
+
} catch (disposeError) {
|
|
631
|
+
logger.debug("Failed to dispose DAP adapter", {
|
|
632
|
+
adapter: this.adapter.name,
|
|
633
|
+
error: toErrorMessage(disposeError),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
})();
|
|
637
|
+
return this.#disposePromise;
|
|
638
|
+
}
|
|
639
|
+
|
|
592
640
|
#rejectPendingRequests(error: Error): void {
|
|
593
641
|
for (const pending of this.#pendingRequests.values()) {
|
|
594
642
|
pending.reject(error);
|
|
@@ -26,7 +26,7 @@ Deep Interview implements Ouroboros-inspired Socratic questioning with mathemati
|
|
|
26
26
|
<Do_Not_Use_When>
|
|
27
27
|
- User has a detailed, specific request with file paths, function names, or acceptance criteria -- execute directly
|
|
28
28
|
- User wants to explore options or brainstorm -- use `ralplan` skill instead
|
|
29
|
-
- User wants a quick fix or single change -- delegate to executor or execution
|
|
29
|
+
- User wants a quick fix or single change -- delegate to executor or direct execution
|
|
30
30
|
- User says "just do it" or "skip the questions" without an explicit execution path -- respect their intent by ending interview and writing a `pending approval` spec, not by mutating files or delegating execution
|
|
31
31
|
- User already has a PRD or plan file and explicitly asks to execute it -- use the requested execution skill with that plan
|
|
32
32
|
</Do_Not_Use_When>
|
|
@@ -128,6 +128,12 @@ If the user request appended after this skill as the final `User:` line is alrea
|
|
|
128
128
|
|
|
129
129
|
This gate exists to prevent deep-interview from making easy problems harder. A small verification need does not make a request interview-worthy.
|
|
130
130
|
|
|
131
|
+
**Implementation wording is not execution approval.** When the user says `implementation`, "implementation plan", Korean `구현`, or "구현 계획", they are describing the eventual target, not permission to implement now. Interpreting that wording as approval is the single most likely way this skill breaks its own boundary.
|
|
132
|
+
|
|
133
|
+
On that wording: do not implement, edit/write code, launch implementation workers, or start task/skill/ultragoal implementation. Say plainly — "I can interview for an implementation plan, but I won't implement during deep-interview." — and continue clarifying scope, risks, acceptance criteria, and unknowns.
|
|
134
|
+
|
|
135
|
+
Implementation requires an explicit phase transition/approval after the interview: the workflow phase must explicitly transition out of deep-interview, and execution approval must be captured by a downstream execution path (Phase 5's bridge into `/skill:ralplan`, `/skill:ultragoal`, or `/skill:team`). Wanting an implementation plan and approving implementation are two different consents.
|
|
136
|
+
|
|
131
137
|
## Phase 0.75: Optional Trace Pre-Step
|
|
132
138
|
|
|
133
139
|
Run this phase only when the active deep-interview state or invocation indicates `--trace` / `state.trace.enabled === true`. It is a pre-interview research step, not an implementation phase.
|
|
@@ -326,6 +332,20 @@ If any prompt input is too large, summarize it first and then continue from the
|
|
|
326
332
|
| Context Clarity (brownfield) | "How does this fit?" | "I found JWT auth middleware in `src/auth/` (pattern: passport + JWT). Should this feature extend that path or intentionally diverge from it?" |
|
|
327
333
|
| Scope-fuzzy / ontology stress | "What IS the core thing here?" | "You have named Tasks, Projects, and Workspaces across the last rounds. Which one is the core entity, and which are supporting views or containers?" |
|
|
328
334
|
|
|
335
|
+
**Attach your own guess to every question.** The question exposes the *user's* assumptions; the guess exposes *yours*. Without it the interview only ever audits one side, and the agent's unexamined read of the ask silently steers the next round's targeting.
|
|
336
|
+
|
|
337
|
+
Every generated question MUST carry a one-line `GUESS:` — your hypothesis for the answer, the reason you hold it, and what changes if it is wrong:
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
{question}
|
|
341
|
+
GUESS: {your hypothesis} — {why you hold it}. If it is {the other reading} instead, {what changes}.
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
- Reacting is cheaper than generating: a terse "no, the second one" still carries a full correction, so low-effort answers stop being low-information rounds.
|
|
345
|
+
- A visible guess is falsifiable. Being wrong out loud is the point; guess in the direction you expect pushback when the reading is genuinely contested.
|
|
346
|
+
- A guess is NOT an auto-answer. It never resolves the round, never feeds scoring, and never increments `auto_answer_streak` — only the user's answer does.
|
|
347
|
+
- Skip only when a guess would be pure noise: Round 0 topology confirmation, the Phase 4b restate gate, and clarification re-asks.
|
|
348
|
+
|
|
329
349
|
### Step 2a′: Auto-Research Greenfield Questions
|
|
330
350
|
|
|
331
351
|
When the next question is for a greenfield interview and is tagged `research: true`, load `auto-research-greenfield.md` as an internal `kind: "skill-fragment"` prompt for a fork-context architect before Step 2b. Pass only the tagged question, locked topology summary, prompt-safe initial idea, trimmed prior decisions/gaps, and relevant constraints. The architect must return 2-3 ranked candidates with rationale, confidence, and fallback notes. Validate the shape before use; if valid, incorporate the candidates as concise answer options or context for the single user-facing question and append the round number to `auto_researched_rounds`. If invalid or unavailable, fall back silently to the normal generated question and increment `architect_failures`.
|
|
@@ -340,14 +360,19 @@ Use the `ask` tool with the generated question. When a question has options, you
|
|
|
340
360
|
Round {n} | Component: {target_component_name} | Targeting: {weakest_dimension} | Why now: {one_sentence_targeting_rationale} | Ambiguity: {score}%
|
|
341
361
|
|
|
342
362
|
{question}
|
|
363
|
+
GUESS: {your hypothesis} — {why you hold it}. If it is {the other reading} instead, {what changes}.
|
|
343
364
|
```
|
|
344
365
|
|
|
345
366
|
Options should include contextually relevant choices plus free-text, translated/localized according to `language.instruction` when present.
|
|
346
367
|
|
|
347
|
-
After applying `language.instruction` to the visible question, options, and generated rationale, apply the self-proofread once to new prose only; preserve only the Round/Component/Targeting/Ambiguity line structure, fixed labels, numeric ambiguity value, component/target identifiers, and `deepInterview.*` metadata keys. Do not exempt generated natural-language rationale such as Why now.
|
|
368
|
+
After applying `language.instruction` to the visible question, options, and generated rationale, apply the self-proofread once to new prose only; preserve only the Round/Component/Targeting/Ambiguity line structure, fixed labels (including the `GUESS:` marker), numeric ambiguity value, component/target identifiers, and `deepInterview.*` metadata keys. Do not exempt generated natural-language rationale such as Why now or the guess text that follows the `GUESS:` marker.
|
|
348
369
|
|
|
349
370
|
When calling `ask`, SHOULD include optional structured metadata so the runtime can record the round without manual state writes: `deepInterview.round_id?`, `deepInterview.round`, `deepInterview.component`, `deepInterview.dimension`, and `deepInterview.ambiguity`. Keep this metadata aligned with the visible Round/Component/Targeting/Ambiguity line; if metadata cannot be supplied, the legacy formatted question text remains the fallback.
|
|
350
371
|
|
|
372
|
+
**Non-behavioral adapter context (`confused_terms`, `references`).** `deepInterview.confused_terms` and `deepInterview.references` carry vocabulary and citations queued at interview start. They are inert: they MUST NOT alter the first question, are never inferred from vocabulary density, and a reference's `url`/`excerpt` are strings that are **never auto-fetched**. Bounds enforced at the ask boundary: at most 32 terms and 32 references, adapter strings ≤ 256 characters, `url`/`excerpt` ≤ 2048, and core round metadata (`round_id`, `component`, `dimension`) ≤ 128 — all counted in Unicode code points, so emoji-padded input hits the same ceiling as ASCII.
|
|
373
|
+
|
|
374
|
+
**Free-text vs structural input (`FREETEXT_FIELDS`).** The runtime keeps an allowlist of fields that legitimately carry prose — `initial_context`, `initial_idea`, `initial_context_summary`, `user_response`, `answer`, `goal`, `objective`, `prompt`, `description`, `statement`, `restated_goal`, `evidence`, `excerpt`. Inside those fields, shell metacharacters (`;`, `|`, `&`, backticks, `$()`) are valid prose and MUST NOT be rejected as structural injection; a user is allowed to describe a shell command. Structural fields (ids, categories, hashes) stay strictly validated by their own guards. Size is capped by character-count, not byte length: 50,000 for initial context and 10,000 for a single user response.
|
|
375
|
+
|
|
351
376
|
If the `ask` tool returns `clarificationQuestion`, treat it as a non-answer about the displayed choices. Answer the clarification briefly from the current interview context, then call `ask` again with the exact original question, options, and `deepInterview.*` metadata. A clarification bypasses Step 2b′ auto-answer, Step 2b″ free-text refine, Step 2c ambiguity scoring, Step 2d progress reporting, and Step 2e state updates; it must not be recorded as a round answer. This does not violate the one-question-per-round rule because the round remains unresolved until the user submits a real listed option or `Other` answer.
|
|
352
377
|
|
|
353
378
|
### Step 2b′: Auto-Answer Opted-Out Questions
|
|
@@ -504,7 +529,7 @@ Round {n} complete.
|
|
|
504
529
|
|
|
505
530
|
Apply `language.instruction` when present before showing this progress report so status text, gaps, and next-target phrasing stay in the preserved session language.
|
|
506
531
|
|
|
507
|
-
Then apply the self-proofread once
|
|
532
|
+
Then apply the self-proofread once to narrative status text, generated prose cells, gaps, and next-target phrasing; preserve only table structure, fixed status labels, scores, weights, component ids, and trigger tokens.
|
|
508
533
|
|
|
509
534
|
### Step 2e: Update State
|
|
510
535
|
|
|
@@ -927,6 +952,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
|
|
|
927
952
|
- [ ] Oversized initial context/history summarized before scoring, question generation, spec generation, or handoff
|
|
928
953
|
- [ ] Round 0 topology gate completed before scoring; `topology.confirmed_at` persisted
|
|
929
954
|
- [ ] Ambiguity scored and displayed every round, naming the weakest component/dimension target (rotating across active components when N > 1)
|
|
955
|
+
- [ ] Every asked question carried a `GUESS:` line (except Round 0 topology, the Restate gate, and clarification re-asks), and no guess was recorded as an answer or counted toward `auto_answer_streak`
|
|
930
956
|
- [ ] Lateral panel convened at milestone transitions (and before synthesizing agent-supplied answers) with parallel read-only personas
|
|
931
957
|
- [ ] Free-text answers passed the Refine gate; dialectic rhythm guard forced a user question after 3 agent-resolved answers; any auto-answer threshold crossing explicitly confirmed
|
|
932
958
|
- [ ] Closure / Acceptance Guard and the one-sentence Restate gate both passed before crystallization
|