@sayknow-cli/coding-agent 0.5.2 → 0.5.8
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 +66 -16
- package/dist/types/cli/update-cli.d.ts +8 -1
- 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 +73 -0
- package/dist/types/dap/client.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +5 -0
- 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/types.d.ts +7 -1
- package/dist/types/runtime-mcp/transports/stdio.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +8 -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/launch-worktree.d.ts +17 -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/system-prompt.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +5 -0
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +13 -7
- package/scripts/compile-args.ts +8 -4
- 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/config/atomic-yaml-patch.ts +167 -30
- package/src/config/keybindings.ts +10 -0
- package/src/config/model-profiles.ts +84 -16
- package/src/config/model-registry.ts +60 -38
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +79 -0
- package/src/config/settings.ts +13 -2
- package/src/dap/client.ts +78 -30
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/lsp/client.ts +17 -29
- package/src/modes/action-registry.ts +2 -0
- package/src/modes/components/welcome.ts +5 -0
- 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/types.ts +5 -1
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/runtime-mcp/transports/stdio.ts +96 -33
- package/src/sdk/broker/lifecycle.ts +6 -5
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +27 -18
- package/src/session/agent-session.ts +155 -15
- 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/launch-worktree.ts +277 -4
- package/src/slash-commands/acp-builtins.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +100 -1
- package/src/slash-commands/types.ts +5 -0
- package/src/system-prompt.ts +7 -0
- package/src/tools/ask.ts +5 -0
- package/src/tools/fetch.ts +7 -2
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +26 -0
- 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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude adapter (issue #3709).
|
|
3
|
+
*
|
|
4
|
+
* Parses two first-party observable Claude transcript/export shapes:
|
|
5
|
+
*
|
|
6
|
+
* 1. `claude-code-jsonl` — Claude Code session transcripts
|
|
7
|
+
* (`~/.claude/projects/<project-slug>/<session-uuid>.jsonl`), one JSON object
|
|
8
|
+
* per line with `type: "user" | "assistant" | "summary" | "system" | …`.
|
|
9
|
+
* 2. `claude-export-json` — the claude.ai data export (`conversations.json`):
|
|
10
|
+
* a JSON array of conversations (or a single conversation object) whose
|
|
11
|
+
* `chat_messages` carry `sender: "human" | "assistant"` and `content` blocks.
|
|
12
|
+
*
|
|
13
|
+
* The same quarantine contract as the Codex adapter applies: unmappable
|
|
14
|
+
* records/messages are counted and digested, never silently dropped.
|
|
15
|
+
*/
|
|
16
|
+
import { type ImportedConversation, type ImportQuarantineRecord, type SessionImportCounts } from "./types";
|
|
17
|
+
export interface ClaudeParseResult {
|
|
18
|
+
conversation: ImportedConversation;
|
|
19
|
+
quarantine: ImportQuarantineRecord[];
|
|
20
|
+
counts: SessionImportCounts;
|
|
21
|
+
redactionKinds: string[];
|
|
22
|
+
}
|
|
23
|
+
/** Parse a Claude Code session JSONL transcript. */
|
|
24
|
+
export declare function parseClaudeCodeTranscript(text: string): ClaudeParseResult;
|
|
25
|
+
/** Parse a claude.ai data-export conversation (array or single object). */
|
|
26
|
+
export declare function parseClaudeExport(text: string): ClaudeParseResult;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex adapter (issue #3709).
|
|
3
|
+
*
|
|
4
|
+
* Parses the Codex CLI rollout transcript: one JSON object per line
|
|
5
|
+
* (`rollout-*.jsonl`, e.g. `~/.codex/sessions/2026/08/01/rollout-….jsonl`).
|
|
6
|
+
* This is Codex's own observable transcript format; the file the user passes is
|
|
7
|
+
* treated as an explicit export and is only read.
|
|
8
|
+
*
|
|
9
|
+
* Strictness contract: records that fail to parse or lack required fields are
|
|
10
|
+
* quarantined with deterministic digests — never silently dropped. Unknown
|
|
11
|
+
* record/event types are mapped when their payload shape is recognized and
|
|
12
|
+
* quarantined otherwise.
|
|
13
|
+
*/
|
|
14
|
+
import { type ImportedConversation, type ImportQuarantineRecord, type SessionImportCounts } from "./types";
|
|
15
|
+
export interface CodexParseResult {
|
|
16
|
+
conversation: ImportedConversation;
|
|
17
|
+
quarantine: ImportQuarantineRecord[];
|
|
18
|
+
counts: SessionImportCounts;
|
|
19
|
+
redactionKinds: string[];
|
|
20
|
+
}
|
|
21
|
+
/** Parse a Codex rollout JSONL transcript into the provider-neutral IR. */
|
|
22
|
+
export declare function parseCodexRollout(text: string): CodexParseResult;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SessionImportCompleted, SessionImportProviderId } from "./types";
|
|
2
|
+
export declare const IMPORT_SESSION_USAGE = "Usage: /import-session <transcript-file> [--provider codex|claude]";
|
|
3
|
+
export type ParsedImportSessionArgs = {
|
|
4
|
+
kind: "ok";
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
provider?: SessionImportProviderId;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "error";
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function parseImportSessionArgs(args: string): ParsedImportSessionArgs;
|
|
12
|
+
export type SessionImportCommandOutcome = {
|
|
13
|
+
kind: "imported";
|
|
14
|
+
result: SessionImportCompleted;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "error";
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
/** Imports a new session only. The caller owns the current-session switch/resume seam. */
|
|
20
|
+
export declare function runSessionImportCommand(args: string, cwd: string): Promise<SessionImportCommandOutcome>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic source-format detection (issue #3709).
|
|
3
|
+
*
|
|
4
|
+
* Detection inspects only the already-read source bytes: a bounded leading
|
|
5
|
+
* sample decides JSONL-vs-JSON and provider by envelope markers. An explicit
|
|
6
|
+
* `--provider` selection narrows candidates; a sample that matches a different
|
|
7
|
+
* provider than requested fails closed with `format_mismatch`. Detection never
|
|
8
|
+
* guesses: a sample matching no supported envelope fails with
|
|
9
|
+
* `unsupported_format` rather than falling back to a speculative parse.
|
|
10
|
+
*/
|
|
11
|
+
import { type SessionImportFormatId, type SessionImportProviderId } from "./types";
|
|
12
|
+
export interface SessionImportDetection {
|
|
13
|
+
provider: SessionImportProviderId;
|
|
14
|
+
format: SessionImportFormatId;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detect the provider/format of an explicit transcript file.
|
|
18
|
+
*
|
|
19
|
+
* @param text Full source text (already size-bounded by the caller).
|
|
20
|
+
* @param requestedProvider Explicit provider selection; undefined = auto.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectSessionImportFormat(text: string, requestedProvider?: SessionImportProviderId): SessionImportDetection;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { type ClaudeParseResult, parseClaudeCodeTranscript, parseClaudeExport } from "./claude";
|
|
2
|
+
export { type CodexParseResult, parseCodexRollout } from "./codex";
|
|
3
|
+
export { IMPORT_SESSION_USAGE, type ParsedImportSessionArgs, parseImportSessionArgs, runSessionImportCommand, type SessionImportCommandOutcome, } from "./command";
|
|
4
|
+
export { detectSessionImportFormat, type SessionImportDetection } from "./detect";
|
|
5
|
+
export { IMPORT_SANITIZER_VERSION, redactImportedText, sanitizeImportedString } from "./redact";
|
|
6
|
+
export { formatSessionImportError, formatSessionImportSummary, IMPORT_CONVERTER_VERSION, importExternalSession, materializeSessionImport, prepareSessionImport, SESSION_IMPORT_COMPLETION_CUSTOM_TYPE, SESSION_IMPORT_CONTEXT_CUSTOM_TYPE, SESSION_IMPORT_PROVENANCE_CUSTOM_TYPE, SESSION_IMPORT_QUARANTINE_CUSTOM_TYPE, SESSION_IMPORT_SOURCE_MAX_BYTES, type SessionImportRequest, type SessionImportTestProbe, } from "./service";
|
|
7
|
+
export * from "./types";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed secret redaction for imported session content (issue #3709).
|
|
3
|
+
*
|
|
4
|
+
* Every string that crosses the import boundary passes through
|
|
5
|
+
* {@link redactImportedText}. Patterns are conservative: a false positive costs
|
|
6
|
+
* one `[REDACTED]` span in reconstructed context, a false negative leaks a
|
|
7
|
+
* credential into a new session transcript, so the balance favors redaction.
|
|
8
|
+
*/
|
|
9
|
+
export declare const IMPORT_REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
10
|
+
/** Bumped when patterns change; persisted in import provenance. */
|
|
11
|
+
export declare const IMPORT_SANITIZER_VERSION = 3;
|
|
12
|
+
export interface ImportRedactionResult {
|
|
13
|
+
value: string;
|
|
14
|
+
redacted: number;
|
|
15
|
+
kinds: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Redact every known credential shape from imported text. The same placeholder
|
|
19
|
+
* is used for all kinds so a partial pattern overlap cannot leak fragments.
|
|
20
|
+
*/
|
|
21
|
+
export declare function redactImportedText(input: string): ImportRedactionResult;
|
|
22
|
+
/** Redact a string, returning only the sanitized value. */
|
|
23
|
+
export declare function sanitizeImportedString(input: string): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type SessionDestinationInput } from "../session/session-manager";
|
|
2
|
+
import { type PreparedSessionImport, type SessionImportCompleted, type SessionImportProviderId } from "./types";
|
|
3
|
+
export declare const IMPORT_CONVERTER_VERSION = 1;
|
|
4
|
+
/** Durable metadata only; it is not added to model context. */
|
|
5
|
+
export declare const SESSION_IMPORT_PROVENANCE_CUSTOM_TYPE = "session-import";
|
|
6
|
+
export declare const SESSION_IMPORT_QUARANTINE_CUSTOM_TYPE = "session-import-quarantine";
|
|
7
|
+
export declare const SESSION_IMPORT_COMPLETION_CUSTOM_TYPE = "session-import-complete";
|
|
8
|
+
/** Displayed custom-message context reconstructed from the imported transcript. */
|
|
9
|
+
export declare const SESSION_IMPORT_CONTEXT_CUSTOM_TYPE = "session-import";
|
|
10
|
+
export declare const SESSION_IMPORT_SOURCE_MAX_BYTES: number;
|
|
11
|
+
export interface SessionImportRequest {
|
|
12
|
+
sourcePath: string;
|
|
13
|
+
provider?: SessionImportProviderId;
|
|
14
|
+
cwd: string;
|
|
15
|
+
destination?: SessionDestinationInput;
|
|
16
|
+
now?: () => Date;
|
|
17
|
+
}
|
|
18
|
+
/** Invocation-scoped mutation seam used only by TOCTOU regression tests. */
|
|
19
|
+
export interface SessionImportTestProbe {
|
|
20
|
+
afterSourceOpen?: (resolvedSourcePath: string) => void | Promise<void>;
|
|
21
|
+
afterSourceIdentityCheck?: (resolvedSourcePath: string) => void | Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** Read, detect, parse, normalize, redact, and bound without session mutation. */
|
|
24
|
+
export declare function prepareSessionImport(request: Pick<SessionImportRequest, "sourcePath" | "provider">, testProbe?: SessionImportTestProbe): Promise<PreparedSessionImport>;
|
|
25
|
+
export declare function importExternalSession(request: SessionImportRequest): Promise<SessionImportCompleted>;
|
|
26
|
+
export declare function materializeSessionImport(prepared: PreparedSessionImport, options: Pick<SessionImportRequest, "cwd" | "destination" | "now">): Promise<SessionImportCompleted>;
|
|
27
|
+
export declare function formatSessionImportSummary(result: SessionImportCompleted): string;
|
|
28
|
+
export declare function formatSessionImportError(error: unknown): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral session-import contract (issue #3709).
|
|
3
|
+
*
|
|
4
|
+
* An import accepts ONE explicit user-selected Codex or Claude transcript/export
|
|
5
|
+
* file, normalizes it into the intermediate representation below, and rebuilds a
|
|
6
|
+
* bounded, redacted continuation context in one workspace-scoped native SKC
|
|
7
|
+
* session. A verified identical import is reused; otherwise a new session is
|
|
8
|
+
* published. The source file is only ever read; provider process state is never scraped.
|
|
9
|
+
*/
|
|
10
|
+
/** Providers with a first-party observable transcript/export format. */
|
|
11
|
+
export type SessionImportProviderId = "codex" | "claude";
|
|
12
|
+
/** Concrete on-disk format variants the adapters understand. */
|
|
13
|
+
export type SessionImportFormatId = "codex-rollout-jsonl" | "claude-code-jsonl" | "claude-export-json";
|
|
14
|
+
/** Maximum quarantined-record digests retained in memory during import. */
|
|
15
|
+
export declare const SESSION_IMPORT_QUARANTINE_MAX_RECORDS = 512;
|
|
16
|
+
/** One normalized conversational unit produced by a provider adapter. */
|
|
17
|
+
export interface ImportedMessage {
|
|
18
|
+
/** Conversational role. Tool evidence is attached to assistant turns. */
|
|
19
|
+
role: "user" | "assistant";
|
|
20
|
+
/** Visible text (may be empty when only tool evidence exists). */
|
|
21
|
+
text: string;
|
|
22
|
+
/** ISO-8601 source timestamp when the record carried one. */
|
|
23
|
+
timestamp?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Bounded, already redacted tool/file evidence lines (e.g. `$ ls` → `ok`).
|
|
26
|
+
* Rendered inline after the text so completed work stays visible.
|
|
27
|
+
*/
|
|
28
|
+
toolEvidence?: string[];
|
|
29
|
+
}
|
|
30
|
+
/** Provider-neutral normalized conversation. */
|
|
31
|
+
export interface ImportedConversation {
|
|
32
|
+
provider: SessionImportProviderId;
|
|
33
|
+
format: SessionImportFormatId;
|
|
34
|
+
/** Source-side session/conversation identifier when the format carries one. */
|
|
35
|
+
sourceSessionId?: string;
|
|
36
|
+
/** Source title when the format carries one. */
|
|
37
|
+
title?: string;
|
|
38
|
+
/** Source workspace cwd when the format carries one. */
|
|
39
|
+
cwd?: string;
|
|
40
|
+
messages: ImportedMessage[];
|
|
41
|
+
}
|
|
42
|
+
/** Deterministic, actionable import failure codes. */
|
|
43
|
+
export type SessionImportErrorCode = "invalid_request" | "source_not_found" | "source_unreadable" | "source_changed" | "unsupported_format" | "format_mismatch" | "malformed_input" | "content_too_large" | "destination_conflict" | "io_failed";
|
|
44
|
+
/** Import pipeline phase a failure belongs to. */
|
|
45
|
+
export type SessionImportPhase = "request" | "read" | "detect" | "parse" | "normalize" | "persist" | "cleanup" | "switch";
|
|
46
|
+
export declare class SessionImportError extends Error {
|
|
47
|
+
readonly code: SessionImportErrorCode;
|
|
48
|
+
readonly phase: SessionImportPhase;
|
|
49
|
+
readonly retryable: boolean;
|
|
50
|
+
readonly limitBytes?: number;
|
|
51
|
+
readonly observedBytes?: number;
|
|
52
|
+
constructor(code: SessionImportErrorCode, phase: SessionImportPhase, message: string, options?: {
|
|
53
|
+
retryable?: boolean;
|
|
54
|
+
limitBytes?: number;
|
|
55
|
+
observedBytes?: number;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** One record the adapter could not map. Never carries raw content. */
|
|
59
|
+
export interface ImportQuarantineRecord {
|
|
60
|
+
/** 1-based record/line number in the source. */
|
|
61
|
+
record: number;
|
|
62
|
+
byteLength: number;
|
|
63
|
+
sha256: string;
|
|
64
|
+
reason: "invalid_json" | "unknown_record" | "missing_fields" | "oversized_record";
|
|
65
|
+
}
|
|
66
|
+
/** Bounded tallies reported to the user and persisted in provenance. */
|
|
67
|
+
export interface SessionImportCounts {
|
|
68
|
+
/** Records successfully mapped into the normalized conversation. */
|
|
69
|
+
mapped: number;
|
|
70
|
+
/** Records quarantined (never silently dropped). */
|
|
71
|
+
quarantined: number;
|
|
72
|
+
/** Secret/credential redactions applied to imported text. */
|
|
73
|
+
redacted: number;
|
|
74
|
+
/** Messages omitted from the rendered context by head/tail bounding. */
|
|
75
|
+
omitted: number;
|
|
76
|
+
}
|
|
77
|
+
/** Provenance persisted as a `custom` session entry on the import target. */
|
|
78
|
+
export interface SessionImportProvenance {
|
|
79
|
+
schemaVersion: 1;
|
|
80
|
+
customType: "session-import";
|
|
81
|
+
provider: SessionImportProviderId;
|
|
82
|
+
format: SessionImportFormatId;
|
|
83
|
+
/** Basename of the explicit user-selected source file (never a full path). */
|
|
84
|
+
sourceFileName: string;
|
|
85
|
+
sourceSessionId?: string;
|
|
86
|
+
sourceTitle?: string;
|
|
87
|
+
/** SHA-256 over the exact source bytes that were imported. */
|
|
88
|
+
sourceSha256: string;
|
|
89
|
+
sourceBytes: number;
|
|
90
|
+
/** RFC 4122 UUID of the SKC session that received the import. */
|
|
91
|
+
targetSessionId: string;
|
|
92
|
+
importedAt: string;
|
|
93
|
+
converterVersion: number;
|
|
94
|
+
sanitizerVersion: number;
|
|
95
|
+
counts: SessionImportCounts;
|
|
96
|
+
/** True when head/tail bounding elided part of the conversation. */
|
|
97
|
+
truncated: boolean;
|
|
98
|
+
quarantine: {
|
|
99
|
+
present: boolean;
|
|
100
|
+
truncated: boolean;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Fully prepared import: parsed, normalized, redacted, bounded. No session state mutated yet. */
|
|
104
|
+
export interface PreparedSessionImport {
|
|
105
|
+
conversation: ImportedConversation;
|
|
106
|
+
/** Rendered continuation context for the `custom_message` entry. */
|
|
107
|
+
contextText: string;
|
|
108
|
+
provenance: Omit<SessionImportProvenance, "targetSessionId" | "importedAt">;
|
|
109
|
+
counts: SessionImportCounts;
|
|
110
|
+
/** Distinct redaction pattern ids that fired (for the user-facing summary). */
|
|
111
|
+
redactionKinds: string[];
|
|
112
|
+
/** Bounded digest-only records for durable quarantine metadata. */
|
|
113
|
+
quarantineRecords: ImportQuarantineRecord[];
|
|
114
|
+
sourceSha256: string;
|
|
115
|
+
sourceBytes: number;
|
|
116
|
+
}
|
|
117
|
+
/** Result of a completed or idempotently reused imported session. */
|
|
118
|
+
export interface SessionImportCompleted {
|
|
119
|
+
targetSessionId: string;
|
|
120
|
+
targetPath: string;
|
|
121
|
+
title: string;
|
|
122
|
+
prepared: PreparedSessionImport;
|
|
123
|
+
/** True when the same source/version import already existed in this workspace. */
|
|
124
|
+
reused: boolean;
|
|
125
|
+
}
|
|
@@ -20,6 +20,9 @@ export interface SkcLaunchWorktreePlan {
|
|
|
20
20
|
detached: boolean;
|
|
21
21
|
baseRef: string;
|
|
22
22
|
branchName: string | null;
|
|
23
|
+
sourceCheckoutRoot: string;
|
|
24
|
+
relativeCwd: string;
|
|
25
|
+
relativeDependencyRoot: string | null;
|
|
23
26
|
}
|
|
24
27
|
export interface SkcLaunchWorktreeResult extends SkcLaunchWorktreePlan {
|
|
25
28
|
created: boolean;
|
|
@@ -36,7 +39,19 @@ export declare function ensureLaunchWorktree(plan: SkcLaunchWorktreePlan | {
|
|
|
36
39
|
}): SkcLaunchWorktreeResult | {
|
|
37
40
|
enabled: false;
|
|
38
41
|
};
|
|
39
|
-
|
|
42
|
+
type WorktreePackageManager = "bun" | "npm" | "pnpm";
|
|
43
|
+
interface WorktreeDependencyProbes {
|
|
44
|
+
isExecutableAvailable?: (name: Exclude<WorktreePackageManager, "bun">) => boolean;
|
|
45
|
+
version?: (name: WorktreePackageManager) => string;
|
|
46
|
+
spawnInstall?: (command: readonly string[], cwd: string) => {
|
|
47
|
+
exitCode: number;
|
|
48
|
+
stderr: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Package worktrees own their complete lockfile-resolved dependency graph. */
|
|
52
|
+
export declare function ensureReusableNodeModules(sourceRoot: string, worktreePath: string, probes?: WorktreeDependencyProbes): "symlink" | "present" | "missing";
|
|
53
|
+
export declare function launchWorktreeCwd(plan: SkcLaunchWorktreePlan): string;
|
|
54
|
+
export declare function ensureLaunchWorktreeDependencies(plan: SkcLaunchWorktreePlan): "symlink" | "present" | "missing";
|
|
40
55
|
/** Result of {@link prepareLaunchWorktree}: the effective working directory, remaining args, and resolved worktree plan. */
|
|
41
56
|
export interface PreparedLaunchWorktree {
|
|
42
57
|
cwd: string;
|
|
@@ -46,3 +61,4 @@ export interface PreparedLaunchWorktree {
|
|
|
46
61
|
};
|
|
47
62
|
}
|
|
48
63
|
export declare function prepareLaunchWorktree(cwd: string, args: string[]): PreparedLaunchWorktree;
|
|
64
|
+
export {};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type ComposerSubmissionOptions } from "../modes/types";
|
|
2
|
+
import { runSessionImportCommand } from "../session-import";
|
|
2
3
|
import type { BuiltinSlashCommand, ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime, SlashCommandSpec, TuiSlashCommandRuntime } from "./types";
|
|
3
4
|
export type { BuiltinSlashCommand, SubcommandDef } from "./types";
|
|
4
5
|
/** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
|
|
5
6
|
export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime & {
|
|
6
7
|
composer?: ComposerSubmissionOptions;
|
|
7
8
|
};
|
|
9
|
+
type SessionImportRunner = typeof runSessionImportCommand;
|
|
10
|
+
export declare function handleImportSessionCommand(command: ParsedSlashCommand, runtime: SlashCommandRuntime, importer?: SessionImportRunner): Promise<SlashCommandResult>;
|
|
11
|
+
export declare function handleImportSessionTuiCommand(command: ParsedSlashCommand, runtime: TuiSlashCommandRuntime, importer?: SessionImportRunner): Promise<SlashCommandResult>;
|
|
8
12
|
export declare function formatUnknownBuiltinSlashCommandDiagnostic(commandName: string): string | undefined;
|
|
9
13
|
/** Builtin command metadata used for slash-command autocomplete and help text. */
|
|
10
14
|
export declare const BUILTIN_SLASH_COMMAND_DEFS: ReadonlyArray<BuiltinSlashCommand>;
|
|
@@ -81,6 +81,11 @@ export interface SlashCommandSpec extends BuiltinSlashCommand {
|
|
|
81
81
|
aliases?: string[];
|
|
82
82
|
/** When false, the dispatcher refuses to handle invocations that include arguments. */
|
|
83
83
|
allowArgs?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether ACP may advertise and execute this command. Commands that read
|
|
86
|
+
* caller-selected local paths must set this to false.
|
|
87
|
+
*/
|
|
88
|
+
acp?: boolean;
|
|
84
89
|
/**
|
|
85
90
|
* ACP-specific override for `description`. Used by `ACP_BUILTIN_SLASH_COMMANDS`
|
|
86
91
|
* when building `available_commands_update` payloads so the client receives
|
|
@@ -83,6 +83,11 @@ export interface BuildSystemPromptOptions {
|
|
|
83
83
|
alwaysApplyRules?: AlwaysApplyRule[];
|
|
84
84
|
/** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
|
|
85
85
|
secretsEnabled?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Reason through technical work in English while answering in the language the
|
|
88
|
+
* user used. Affects prompt guidance only. Default: "off".
|
|
89
|
+
*/
|
|
90
|
+
reasoningLanguage?: "off" | "english";
|
|
86
91
|
/** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
87
92
|
workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
|
|
88
93
|
/**
|
|
@@ -250,6 +250,11 @@ export declare class AskTool implements AgentTool<typeof askSchema, AskToolDetai
|
|
|
250
250
|
}, z.core.$strip>>;
|
|
251
251
|
}, z.core.$strip>;
|
|
252
252
|
readonly strict = true;
|
|
253
|
+
/**
|
|
254
|
+
* These fields are rendered to the user and carry no executable or durable
|
|
255
|
+
* meaning. IDs and workflow/deep-interview metadata remain fail-closed.
|
|
256
|
+
*/
|
|
257
|
+
readonly displaySafeEscapedArgFields: readonly ["questions.question", "questions.options.label"];
|
|
253
258
|
readonly loadMode = "discoverable";
|
|
254
259
|
constructor(session: ToolSession);
|
|
255
260
|
static createIf(session: ToolSession): AskTool | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureMupdfWasmResolution(): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.8",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "bun scripts/build-binary.ts",
|
|
35
35
|
"check": "biome check . && bun run check:types",
|
|
36
|
+
"check:runtime": "bun run generate-hotkeys-docs --check",
|
|
36
37
|
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
37
38
|
"lint": "biome lint .",
|
|
38
39
|
"test": "bun test --isolate",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"fmt": "biome format --write . && bun run format-prompts",
|
|
44
45
|
"format-prompts": "bun scripts/format-prompts.ts",
|
|
45
46
|
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
|
47
|
+
"generate-hotkeys-docs": "bun scripts/generate-hotkeys-docs.ts",
|
|
46
48
|
"prepack": "bun scripts/generate-docs-index.ts",
|
|
47
49
|
"generate-template": "bun scripts/generate-template.ts",
|
|
48
50
|
"install:defaults": "bun src/cli.ts setup defaults",
|
|
@@ -52,12 +54,12 @@
|
|
|
52
54
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
53
55
|
"@babel/parser": "^7.29.3",
|
|
54
56
|
"@mozilla/readability": "^0.6.0",
|
|
55
|
-
"@sayknow-cli/stats": "0.5.
|
|
56
|
-
"@sayknow-cli/agent-core": "0.5.
|
|
57
|
-
"@sayknow-cli/ai": "0.5.
|
|
58
|
-
"@sayknow-cli/natives": "0.5.
|
|
59
|
-
"@sayknow-cli/tui": "0.5.
|
|
60
|
-
"@sayknow-cli/utils": "0.5.
|
|
57
|
+
"@sayknow-cli/stats": "0.5.8",
|
|
58
|
+
"@sayknow-cli/agent-core": "0.5.8",
|
|
59
|
+
"@sayknow-cli/ai": "0.5.8",
|
|
60
|
+
"@sayknow-cli/natives": "0.5.8",
|
|
61
|
+
"@sayknow-cli/tui": "0.5.8",
|
|
62
|
+
"@sayknow-cli/utils": "0.5.8",
|
|
61
63
|
"@puppeteer/browsers": "^2.13.0",
|
|
62
64
|
"@types/turndown": "5.0.6",
|
|
63
65
|
"@xterm/headless": "^6.0.0",
|
|
@@ -88,6 +90,10 @@
|
|
|
88
90
|
"README.md",
|
|
89
91
|
"CHANGELOG.md",
|
|
90
92
|
"vendor",
|
|
93
|
+
"!vendor/insane-search/engine/tests/**",
|
|
94
|
+
"!src/export/html/template.html",
|
|
95
|
+
"!src/export/html/template.css",
|
|
96
|
+
"!src/export/html/template.js",
|
|
91
97
|
"dist/types"
|
|
92
98
|
],
|
|
93
99
|
"exports": {
|
package/scripts/compile-args.ts
CHANGED
|
@@ -18,7 +18,14 @@ const compiledDefineFlags = ['process.env.PI_COMPILED="true"'];
|
|
|
18
18
|
const releaseDefineFlags = [...compiledDefineFlags, 'process.env.SKC_BUILD_CHANNEL="release"'];
|
|
19
19
|
const devDefineFlags = [...compiledDefineFlags, 'process.env.SKC_BUILD_CHANNEL="dev"'];
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// NOTE: mupdf must NOT be marked --external here. Compiled binaries cannot
|
|
22
|
+
// resolve bare "mupdf" imports (and its Emscripten loader cannot find the
|
|
23
|
+
// wasm asset inside the bunfs), which broke markit PDF conversion in every
|
|
24
|
+
// standalone release (upstream #5433). mupdf is bundled instead: its wasm is
|
|
25
|
+
// embedded via `with { type: "file" }` and routed to the loader through the
|
|
26
|
+
// hook in packages/coding-agent/src/utils/mupdf-wasm.ts, and the bundled
|
|
27
|
+
// `require("mupdf")` inside markit-ai is replaced by the patch in
|
|
28
|
+
// patches/markit-ai@0.5.3.patch.
|
|
22
29
|
|
|
23
30
|
export const releaseEntrypoints = [
|
|
24
31
|
"./packages/coding-agent/src/cli.ts",
|
|
@@ -57,8 +64,6 @@ export function buildReleaseCompileArgs(target: string, outfile: string): string
|
|
|
57
64
|
outfile,
|
|
58
65
|
target,
|
|
59
66
|
defines: releaseDefineFlags,
|
|
60
|
-
|
|
61
|
-
externals: compiledExternalPackages,
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -68,7 +73,6 @@ export function buildDevCompileArgs(outfile = "dist/skc"): string[] {
|
|
|
68
73
|
entrypoints: devEntrypoints,
|
|
69
74
|
outfile,
|
|
70
75
|
defines: devDefineFlags,
|
|
71
|
-
externals: compiledExternalPackages,
|
|
72
76
|
});
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -34,6 +34,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
34
34
|
"slash_command:pet": "visual/local-only command, not a user-facing SDK control seam",
|
|
35
35
|
"slash_command:transcript": "visual/local-only transcript viewer, not a user-facing SDK control seam",
|
|
36
36
|
"slash_command:sessions": "visual/local-only sessions dashboard, not a user-facing SDK control seam",
|
|
37
|
+
"slash_command:import-session":
|
|
38
|
+
"local transcript-file import into a new session; ACP is explicitly disabled and no SDK operation reads caller-selected host paths",
|
|
37
39
|
"agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
|
|
38
40
|
"agent_session:nextToolChoice": "internal accessor/plumbing, not a user-facing control seam",
|
|
39
41
|
"agent_session:setForcedToolChoice": "internal accessor/plumbing, not a user-facing control seam",
|
|
@@ -51,6 +53,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
51
53
|
"agent_session:clearPlanCompactAbortPending": "internal accessor/plumbing, not a user-facing control seam",
|
|
52
54
|
"agent_session:enqueueCustomMessageDisplay": "internal accessor/plumbing, not a user-facing control seam",
|
|
53
55
|
"agent_session:runMidRunMaintenanceForTests": "test-only maintenance seam, not a user-facing SDK control seam",
|
|
56
|
+
"agent_session:runPrePromptContextCheckForTests": "test-only context check seam, not a user-facing SDK control seam",
|
|
54
57
|
"agent_session:estimateMidRunContextTokensForTests": "test-only estimator seam, not a user-facing SDK control seam",
|
|
55
58
|
"agent_session:activeMidRunBarrierCountForTests": "read-only test seam, not a user-facing SDK control seam",
|
|
56
59
|
"agent_session:activeMidRunMaintenanceCountForTests": "read-only test seam, not a user-facing SDK control seam",
|
|
@@ -58,6 +61,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
58
61
|
"agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
|
|
59
62
|
"test-only cancellation seam, not a user-facing SDK control seam",
|
|
60
63
|
"agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
|
|
64
|
+
"agent_session:hasExplicitlyDisabledTools":
|
|
65
|
+
"internal automatic-tool-selection predicate, not a user-facing SDK control seam",
|
|
61
66
|
"agent_session:emitNotice": "internal accessor/plumbing, not a user-facing control seam",
|
|
62
67
|
"agent_session:subscribe": "internal accessor/plumbing, not a user-facing control seam",
|
|
63
68
|
"agent_session:dispose": "internal accessor/plumbing, not a user-facing control seam",
|
|
@@ -275,9 +280,6 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
|
|
|
275
280
|
"slash_command:move": "session.cwd.move",
|
|
276
281
|
};
|
|
277
282
|
|
|
278
|
-
/** Genuine action seams awaiting a reviewed registry mapping or exclusion. */
|
|
279
|
-
const PENDING_REVIEW: Readonly<Record<string, string>> = {};
|
|
280
|
-
|
|
281
283
|
function lockedExclusion(sourceId: string): string | undefined {
|
|
282
284
|
return LOCKED_EXCLUSIONS[sourceId];
|
|
283
285
|
}
|
|
@@ -812,7 +814,6 @@ function generatedRecords(seams: Awaited<ReturnType<typeof scanSeams>>): Invento
|
|
|
812
814
|
for (const seam of seams) {
|
|
813
815
|
const sdkId = SEAM_TO_SDK[seam.sourceId];
|
|
814
816
|
const rationale = lockedExclusion(seam.sourceId);
|
|
815
|
-
if (!sdkId && !rationale) continue;
|
|
816
817
|
if (!sdkId) {
|
|
817
818
|
if (!rationale) continue;
|
|
818
819
|
records.push({
|
|
@@ -877,8 +878,6 @@ export function pendingReviewErrors(seams: readonly Pick<SourceSeam, "sourceId">
|
|
|
877
878
|
|
|
878
879
|
async function check(records: InventoryRecord[], seams: Awaited<ReturnType<typeof scanSeams>>): Promise<void> {
|
|
879
880
|
const errors = [...validateRegistry(records), ...pendingReviewErrors(seams)];
|
|
880
|
-
for (const sourceId of Object.keys(PENDING_REVIEW))
|
|
881
|
-
errors.push(`Pending review source seam: ${sourceId}. ${PENDING_REVIEW[sourceId]}`);
|
|
882
881
|
let checkedIn: InventoryRecord[];
|
|
883
882
|
try {
|
|
884
883
|
checkedIn = await Bun.file(inventoryPath).json();
|
package/src/cli/config-cli.ts
CHANGED
|
@@ -88,6 +88,10 @@ function redactConfigValue(path: string, value: unknown, showSecrets?: boolean):
|
|
|
88
88
|
}
|
|
89
89
|
return REDACTED_SECRET_VALUE;
|
|
90
90
|
}
|
|
91
|
+
function reportConfigPersistenceFailure(): never {
|
|
92
|
+
console.error(chalk.red("Failed to persist configuration."));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
91
95
|
|
|
92
96
|
/** Find setting definition by path */
|
|
93
97
|
function findSettingDef(path: string): CliSettingDef | undefined {
|
|
@@ -223,25 +227,25 @@ function parseAndSetValue(path: SettingPath, rawValue: string): void {
|
|
|
223
227
|
const lower = trimmed.toLowerCase();
|
|
224
228
|
if (["true", "1", "yes", "on"].includes(lower)) parsedValue = true;
|
|
225
229
|
else if (["false", "0", "no", "off"].includes(lower)) parsedValue = false;
|
|
226
|
-
else throw new Error(
|
|
230
|
+
else throw new Error("Invalid boolean value. Use true/false, yes/no, on/off, or 1/0");
|
|
227
231
|
break;
|
|
228
232
|
}
|
|
229
233
|
case "number": {
|
|
230
234
|
parsedValue = Number(trimmed);
|
|
231
|
-
if (!Number.isFinite(parsedValue)) throw new Error(
|
|
235
|
+
if (!Number.isFinite(parsedValue)) throw new Error("Invalid number");
|
|
232
236
|
const validate =
|
|
233
237
|
"validate" in SETTINGS_SCHEMA[path]
|
|
234
238
|
? (SETTINGS_SCHEMA[path].validate as ((value: number) => boolean) | undefined)
|
|
235
239
|
: undefined;
|
|
236
240
|
if (validate?.(parsedValue as number) === false) {
|
|
237
|
-
throw new Error(`Invalid number for ${path}
|
|
241
|
+
throw new Error(`Invalid number for ${path}`);
|
|
238
242
|
}
|
|
239
243
|
break;
|
|
240
244
|
}
|
|
241
245
|
case "enum": {
|
|
242
246
|
const valid = getEnumValues(path);
|
|
243
247
|
if (valid && !valid.includes(trimmed)) {
|
|
244
|
-
throw new Error(`Invalid value
|
|
248
|
+
throw new Error(`Invalid value. Valid values: ${valid.join(", ")}`);
|
|
245
249
|
}
|
|
246
250
|
parsedValue = trimmed;
|
|
247
251
|
break;
|
|
@@ -251,10 +255,10 @@ function parseAndSetValue(path: SettingPath, rawValue: string): void {
|
|
|
251
255
|
try {
|
|
252
256
|
parsed = JSON.parse(trimmed);
|
|
253
257
|
} catch {
|
|
254
|
-
throw new Error(
|
|
258
|
+
throw new Error("Invalid array JSON");
|
|
255
259
|
}
|
|
256
260
|
if (!Array.isArray(parsed)) {
|
|
257
|
-
throw new Error(
|
|
261
|
+
throw new Error("Invalid array JSON");
|
|
258
262
|
}
|
|
259
263
|
parsedValue = parsed;
|
|
260
264
|
break;
|
|
@@ -264,10 +268,10 @@ function parseAndSetValue(path: SettingPath, rawValue: string): void {
|
|
|
264
268
|
try {
|
|
265
269
|
parsed = JSON.parse(trimmed);
|
|
266
270
|
} catch {
|
|
267
|
-
throw new Error(
|
|
271
|
+
throw new Error("Invalid record JSON");
|
|
268
272
|
}
|
|
269
273
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
270
|
-
throw new Error(
|
|
274
|
+
throw new Error("Invalid record JSON");
|
|
271
275
|
}
|
|
272
276
|
parsedValue = parsed;
|
|
273
277
|
break;
|
|
@@ -408,6 +412,11 @@ async function handleSet(
|
|
|
408
412
|
console.error(chalk.red(String(err)));
|
|
409
413
|
process.exit(1);
|
|
410
414
|
}
|
|
415
|
+
try {
|
|
416
|
+
await settings.flushOrThrow();
|
|
417
|
+
} catch {
|
|
418
|
+
reportConfigPersistenceFailure();
|
|
419
|
+
}
|
|
411
420
|
|
|
412
421
|
const newValue = settings.get(def.path);
|
|
413
422
|
const displayValue = redactConfigValue(def.path, newValue, flags.showSecrets);
|
|
@@ -438,6 +447,12 @@ async function handleReset(key: string | undefined, flags: { json?: boolean }):
|
|
|
438
447
|
if (defaultValue === undefined) settings.unset(path);
|
|
439
448
|
else settings.set(path, defaultValue as SettingValue<typeof path>);
|
|
440
449
|
|
|
450
|
+
try {
|
|
451
|
+
await settings.flushOrThrow();
|
|
452
|
+
} catch {
|
|
453
|
+
reportConfigPersistenceFailure();
|
|
454
|
+
}
|
|
455
|
+
|
|
441
456
|
if (flags.json) {
|
|
442
457
|
console.log(JSON.stringify({ key: def.path, value: defaultValue }));
|
|
443
458
|
} else {
|