@indianic/dialout 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-adapters/augment.d.ts +3 -0
- package/dist/ai-adapters/augment.d.ts.map +1 -0
- package/dist/ai-adapters/augment.js +147 -0
- package/dist/ai-adapters/augment.js.map +1 -0
- package/dist/ai-adapters/index.d.ts.map +1 -1
- package/dist/ai-adapters/index.js +2 -0
- package/dist/ai-adapters/index.js.map +1 -1
- package/dist/ai-json-tail.d.ts +26 -0
- package/dist/ai-json-tail.d.ts.map +1 -0
- package/dist/ai-json-tail.js +153 -0
- package/dist/ai-json-tail.js.map +1 -0
- package/dist/ai-session-detector.d.ts +22 -4
- package/dist/ai-session-detector.d.ts.map +1 -1
- package/dist/ai-session-detector.js +43 -6
- package/dist/ai-session-detector.js.map +1 -1
- package/dist/ai-sessions.d.ts +4 -2
- package/dist/ai-sessions.d.ts.map +1 -1
- package/dist/ai-sessions.js +24 -8
- package/dist/ai-sessions.js.map +1 -1
- package/dist/ai-transcript-locator.d.ts +74 -0
- package/dist/ai-transcript-locator.d.ts.map +1 -1
- package/dist/ai-transcript-locator.js +181 -0
- package/dist/ai-transcript-locator.js.map +1 -1
- package/dist/ai-transcript-tail.d.ts +17 -1
- package/dist/ai-transcript-tail.d.ts.map +1 -1
- package/dist/ai-transcript-tail.js.map +1 -1
- package/dist/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +1 -0
- package/dist/cli-client.js.map +1 -1
- package/dist/cli.js +96 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +21 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -2
- package/dist/config.js.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.d.ts +97 -0
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.js +147 -0
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/custom-agents.test.js +155 -0
- package/node_modules/@indianic/dialout-shared/dist/index.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/index.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/index.js +11 -2
- package/node_modules/@indianic/dialout-shared/dist/kinds.d.ts +1 -1
- package/node_modules/@indianic/dialout-shared/dist/kinds.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.js +20 -5
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.test.js +75 -0
- package/node_modules/@indianic/dialout-shared/package.json +1 -1
- package/node_modules/@indianic/dialout-shared/src/.impeccable/hook.cache.json +1 -0
- package/node_modules/@indianic/dialout-shared/src/.omc/state/sessions/e864cc5c-dad7-49c4-8666-6d7fb2297d2a/pre-tool-advisory-throttle.json +14 -0
- package/node_modules/@indianic/dialout-shared/src/custom-agents.test.ts +185 -0
- package/node_modules/@indianic/dialout-shared/src/custom-agents.ts +198 -0
- package/node_modules/@indianic/dialout-shared/src/index.ts +5 -0
- package/node_modules/@indianic/dialout-shared/src/kinds.ts +1 -1
- package/node_modules/@indianic/dialout-shared/src/pane-prompt.test.ts +82 -0
- package/node_modules/@indianic/dialout-shared/src/pane-prompt.ts +21 -2
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AiKind } from './kinds';
|
|
2
|
+
/**
|
|
3
|
+
* A coding agent this machine's owner has declared by hand.
|
|
4
|
+
*
|
|
5
|
+
* Two cases this exists for, and one it deliberately does not cover.
|
|
6
|
+
*
|
|
7
|
+
* It covers a RENAMED BINARY — a copy of a supported CLI living under another
|
|
8
|
+
* name, where argv really is `sagent` and the detector's rules cannot see it.
|
|
9
|
+
* And it covers a supported format written somewhere we do not look.
|
|
10
|
+
*
|
|
11
|
+
* It does NOT make a genuinely new vendor readable. `kind` chooses which
|
|
12
|
+
* existing parser runs, so a custom agent must write a transcript one of the
|
|
13
|
+
* adapters already understands. There is no way around that: knowing a process
|
|
14
|
+
* is an agent tells you nothing about how to read its conversation.
|
|
15
|
+
*
|
|
16
|
+
* Worth being clear about what is NOT needed here: a shell alias, function or
|
|
17
|
+
* wrapper script needs no entry at all. `sagent() { claude "$@"; }` runs a
|
|
18
|
+
* process whose argv is `claude`, and findAgentInPane walks descendants, so it
|
|
19
|
+
* is already detected. Only a renamed binary is invisible.
|
|
20
|
+
*
|
|
21
|
+
* ---
|
|
22
|
+
*
|
|
23
|
+
* MACHINE-LOCAL BY DESIGN. These live in ~/.dialout/config.json and are never
|
|
24
|
+
* accepted from the server.
|
|
25
|
+
*
|
|
26
|
+
* The agent runs as the user, with the user's full read access, and it streams
|
|
27
|
+
* whatever it is pointed at into a chat. A server-supplied path would therefore
|
|
28
|
+
* be a file-read primitive: point it at ~/.ssh/id_rsa or a .env and the
|
|
29
|
+
* contents leave the machine. Keeping the declaration on the machine means
|
|
30
|
+
* compromising a web account cannot turn every agent into a file reader — the
|
|
31
|
+
* same reasoning that keeps the ws-server bound to 127.0.0.1 and filters every
|
|
32
|
+
* value interpolated into the cowork rc block.
|
|
33
|
+
*
|
|
34
|
+
* The validation below is the second line of defence, not the first.
|
|
35
|
+
*/
|
|
36
|
+
export interface CustomAgent {
|
|
37
|
+
/** The command name as the process table shows it, e.g. 'sagent'. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Which existing adapter reads this agent's transcript. */
|
|
40
|
+
kind: AiKind;
|
|
41
|
+
/** Where the transcript lives; see expandTranscript for the placeholders. */
|
|
42
|
+
transcript: string;
|
|
43
|
+
}
|
|
44
|
+
export declare const AI_KINDS: readonly AiKind[];
|
|
45
|
+
export declare function isAiKind(value: unknown): value is AiKind;
|
|
46
|
+
/** The placeholders expandTranscript understands. Anything else is a typo worth reporting. */
|
|
47
|
+
export declare const TRANSCRIPT_VARS: readonly ["home", "cwd", "tty", "pid"];
|
|
48
|
+
export type CustomAgentParse = {
|
|
49
|
+
ok: true;
|
|
50
|
+
agent: CustomAgent;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
export declare function parseCustomAgent(input: unknown): CustomAgentParse;
|
|
56
|
+
/** Parses a list, reporting the first bad entry rather than silently dropping it. */
|
|
57
|
+
export declare function parseCustomAgents(input: unknown): {
|
|
58
|
+
ok: true;
|
|
59
|
+
agents: CustomAgent[];
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
error: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The detector rule for a custom name.
|
|
66
|
+
*
|
|
67
|
+
* Built with the same path-segment anchoring as the built-in rules, so
|
|
68
|
+
* `sagent` and `/usr/local/bin/sagent` match while `vim sagent` and
|
|
69
|
+
* `sagentry` do not. The name is escaped, so a declaration can never inject
|
|
70
|
+
* a pattern — `.*` is matched literally, not as a wildcard.
|
|
71
|
+
*/
|
|
72
|
+
export declare function customAgentRule(agent: CustomAgent): RegExp;
|
|
73
|
+
export interface TranscriptVars {
|
|
74
|
+
home: string;
|
|
75
|
+
cwd?: string;
|
|
76
|
+
tty?: string;
|
|
77
|
+
pid?: number;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fill in a transcript template.
|
|
81
|
+
*
|
|
82
|
+
* Returns null when a placeholder the template uses has no value — an
|
|
83
|
+
* unresolved `{cwd}` would otherwise be looked up as a literal directory
|
|
84
|
+
* called "{cwd}", which silently never matches and looks like the user's
|
|
85
|
+
* declaration was wrong.
|
|
86
|
+
*/
|
|
87
|
+
export declare function expandTranscript(template: string, vars: TranscriptVars): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Split an expanded template into the directory to list and a matcher for the
|
|
90
|
+
* file name. A template with no `*` still goes through this, so one code path
|
|
91
|
+
* handles both.
|
|
92
|
+
*/
|
|
93
|
+
export declare function transcriptMatcher(expanded: string): {
|
|
94
|
+
dir: string;
|
|
95
|
+
match: (name: string) => boolean;
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=custom-agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-agents.d.ts","sourceRoot":"","sources":["../src/custom-agents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,QAAQ,EAAE,SAAS,MAAM,EAAoD,CAAC;AAE3F,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD;AAWD,8FAA8F;AAC9F,eAAO,MAAM,eAAe,wCAAyC,CAAC;AAGtE,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAkDjE;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAepH;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE1D;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAYtF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,CASrG"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TRANSCRIPT_VARS = exports.AI_KINDS = void 0;
|
|
4
|
+
exports.isAiKind = isAiKind;
|
|
5
|
+
exports.parseCustomAgent = parseCustomAgent;
|
|
6
|
+
exports.parseCustomAgents = parseCustomAgents;
|
|
7
|
+
exports.customAgentRule = customAgentRule;
|
|
8
|
+
exports.expandTranscript = expandTranscript;
|
|
9
|
+
exports.transcriptMatcher = transcriptMatcher;
|
|
10
|
+
exports.AI_KINDS = ['claude', 'codex', 'grok', 'augment'];
|
|
11
|
+
function isAiKind(value) {
|
|
12
|
+
return typeof value === 'string' && exports.AI_KINDS.includes(value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A command name, not a pattern.
|
|
16
|
+
*
|
|
17
|
+
* No spaces and no path separators: the value is matched against one argv
|
|
18
|
+
* token, and anything looser invites an entry that matches every process on
|
|
19
|
+
* the machine. The characters allowed are the ones real binaries use.
|
|
20
|
+
*/
|
|
21
|
+
const NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/;
|
|
22
|
+
/** The placeholders expandTranscript understands. Anything else is a typo worth reporting. */
|
|
23
|
+
exports.TRANSCRIPT_VARS = ['home', 'cwd', 'tty', 'pid'];
|
|
24
|
+
const VAR_RE = /\{([a-z]+)\}/g;
|
|
25
|
+
function parseCustomAgent(input) {
|
|
26
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) {
|
|
27
|
+
return { ok: false, error: 'A custom agent must be an object' };
|
|
28
|
+
}
|
|
29
|
+
const raw = input;
|
|
30
|
+
const name = typeof raw.name === 'string' ? raw.name.trim() : '';
|
|
31
|
+
const kind = typeof raw.kind === 'string' ? raw.kind.trim().toLowerCase() : '';
|
|
32
|
+
const transcript = typeof raw.transcript === 'string' ? raw.transcript.trim() : '';
|
|
33
|
+
if (!NAME_RE.test(name)) {
|
|
34
|
+
return { ok: false, error: 'name must be a bare command name, e.g. sagent (letters, digits, . _ + -)' };
|
|
35
|
+
}
|
|
36
|
+
if (!isAiKind(kind)) {
|
|
37
|
+
return { ok: false, error: `kind must be one of ${exports.AI_KINDS.join(', ')}` };
|
|
38
|
+
}
|
|
39
|
+
if (!transcript) {
|
|
40
|
+
return { ok: false, error: 'transcript is required' };
|
|
41
|
+
}
|
|
42
|
+
// Anchored at {home} so a declaration cannot reach outside the user's own
|
|
43
|
+
// files even before the resolved path is re-checked.
|
|
44
|
+
if (!transcript.startsWith('{home}/')) {
|
|
45
|
+
return { ok: false, error: 'transcript must start with {home}/' };
|
|
46
|
+
}
|
|
47
|
+
if (transcript.includes('..')) {
|
|
48
|
+
return { ok: false, error: 'transcript must not contain ..' };
|
|
49
|
+
}
|
|
50
|
+
// A transcript is a file we parse, so the extension is part of the contract.
|
|
51
|
+
// Without this, `{home}/.ssh/*` is a legal declaration.
|
|
52
|
+
if (!/\.jsonl?$/.test(transcript)) {
|
|
53
|
+
return { ok: false, error: 'transcript must end in .json or .jsonl' };
|
|
54
|
+
}
|
|
55
|
+
// At most one glob, and only in the filename: a `*` in a directory segment
|
|
56
|
+
// would walk the home directory.
|
|
57
|
+
const slash = transcript.lastIndexOf('/');
|
|
58
|
+
if (transcript.slice(0, slash).includes('*')) {
|
|
59
|
+
return { ok: false, error: 'a * may only appear in the file name, not in a directory' };
|
|
60
|
+
}
|
|
61
|
+
if ((transcript.match(/\*/g) || []).length > 1) {
|
|
62
|
+
return { ok: false, error: 'transcript may contain at most one *' };
|
|
63
|
+
}
|
|
64
|
+
const unknown = [...transcript.matchAll(VAR_RE)]
|
|
65
|
+
.map((m) => m[1])
|
|
66
|
+
.filter((v) => !exports.TRANSCRIPT_VARS.includes(v));
|
|
67
|
+
if (unknown.length) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
error: `unknown placeholder {${unknown[0]}}; use ${exports.TRANSCRIPT_VARS.map((v) => `{${v}}`).join(', ')}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { ok: true, agent: { name, kind, transcript } };
|
|
74
|
+
}
|
|
75
|
+
/** Parses a list, reporting the first bad entry rather than silently dropping it. */
|
|
76
|
+
function parseCustomAgents(input) {
|
|
77
|
+
if (input === undefined || input === null)
|
|
78
|
+
return { ok: true, agents: [] };
|
|
79
|
+
if (!Array.isArray(input))
|
|
80
|
+
return { ok: false, error: 'customAgents must be an array' };
|
|
81
|
+
const agents = [];
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
for (const entry of input) {
|
|
84
|
+
const parsed = parseCustomAgent(entry);
|
|
85
|
+
if (!parsed.ok)
|
|
86
|
+
return parsed;
|
|
87
|
+
if (seen.has(parsed.agent.name)) {
|
|
88
|
+
return { ok: false, error: `duplicate custom agent name: ${parsed.agent.name}` };
|
|
89
|
+
}
|
|
90
|
+
seen.add(parsed.agent.name);
|
|
91
|
+
agents.push(parsed.agent);
|
|
92
|
+
}
|
|
93
|
+
return { ok: true, agents };
|
|
94
|
+
}
|
|
95
|
+
function escapeRegExp(text) {
|
|
96
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The detector rule for a custom name.
|
|
100
|
+
*
|
|
101
|
+
* Built with the same path-segment anchoring as the built-in rules, so
|
|
102
|
+
* `sagent` and `/usr/local/bin/sagent` match while `vim sagent` and
|
|
103
|
+
* `sagentry` do not. The name is escaped, so a declaration can never inject
|
|
104
|
+
* a pattern — `.*` is matched literally, not as a wildcard.
|
|
105
|
+
*/
|
|
106
|
+
function customAgentRule(agent) {
|
|
107
|
+
return new RegExp(`(^|/)${escapeRegExp(agent.name)}(\\s|$)`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Fill in a transcript template.
|
|
111
|
+
*
|
|
112
|
+
* Returns null when a placeholder the template uses has no value — an
|
|
113
|
+
* unresolved `{cwd}` would otherwise be looked up as a literal directory
|
|
114
|
+
* called "{cwd}", which silently never matches and looks like the user's
|
|
115
|
+
* declaration was wrong.
|
|
116
|
+
*/
|
|
117
|
+
function expandTranscript(template, vars) {
|
|
118
|
+
let missing = false;
|
|
119
|
+
const out = template.replace(VAR_RE, (_whole, name) => {
|
|
120
|
+
const value = name === 'home' ? vars.home
|
|
121
|
+
: name === 'cwd' ? vars.cwd
|
|
122
|
+
: name === 'tty' ? vars.tty
|
|
123
|
+
: name === 'pid' ? (vars.pid === undefined ? undefined : String(vars.pid))
|
|
124
|
+
: undefined;
|
|
125
|
+
if (!value) {
|
|
126
|
+
missing = true;
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
});
|
|
131
|
+
return missing ? null : out;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Split an expanded template into the directory to list and a matcher for the
|
|
135
|
+
* file name. A template with no `*` still goes through this, so one code path
|
|
136
|
+
* handles both.
|
|
137
|
+
*/
|
|
138
|
+
function transcriptMatcher(expanded) {
|
|
139
|
+
const slash = expanded.lastIndexOf('/');
|
|
140
|
+
const dir = expanded.slice(0, slash) || '/';
|
|
141
|
+
const base = expanded.slice(slash + 1);
|
|
142
|
+
if (!base.includes('*')) {
|
|
143
|
+
return { dir, match: (name) => name === base };
|
|
144
|
+
}
|
|
145
|
+
const re = new RegExp(`^${base.split('*').map(escapeRegExp).join('[^/]*')}$`);
|
|
146
|
+
return { dir, match: (name) => re.test(name) };
|
|
147
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-agents.test.d.ts","sourceRoot":"","sources":["../src/custom-agents.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const custom_agents_1 = require("./custom-agents");
|
|
5
|
+
const good = {
|
|
6
|
+
name: 'sagent',
|
|
7
|
+
kind: 'claude',
|
|
8
|
+
transcript: '{home}/.myagent/sessions/*.jsonl',
|
|
9
|
+
};
|
|
10
|
+
(0, vitest_1.describe)('parseCustomAgent', () => {
|
|
11
|
+
(0, vitest_1.it)('accepts a well-formed declaration', () => {
|
|
12
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)(good);
|
|
13
|
+
(0, vitest_1.expect)(parsed).toEqual({ ok: true, agent: good });
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('trims and lowercases the kind, so "Claude" is not a mystery failure', () => {
|
|
16
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, kind: ' Claude ' });
|
|
17
|
+
(0, vitest_1.expect)(parsed.ok && parsed.agent.kind).toBe('claude');
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('rejects a kind with no adapter behind it', () => {
|
|
20
|
+
// The whole point: `kind` chooses which parser runs. A kind we have no
|
|
21
|
+
// adapter for would produce a session with no conversation in it.
|
|
22
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, kind: 'augment-next' });
|
|
23
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.describe)('the path rules, which are the security boundary', () => {
|
|
26
|
+
// The agent runs as the user and streams whatever it is pointed at into a
|
|
27
|
+
// chat. These rules are what stop a declaration being a way to read
|
|
28
|
+
// arbitrary files.
|
|
29
|
+
(0, vitest_1.it)('requires the path to start at {home}', () => {
|
|
30
|
+
for (const transcript of ['/etc/passwd', '~/.ssh/id_rsa', '/{home}/x.jsonl', 'relative/x.jsonl']) {
|
|
31
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, transcript });
|
|
32
|
+
(0, vitest_1.expect)(parsed.ok, transcript).toBe(false);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)('rejects traversal outright', () => {
|
|
36
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/../../etc/hosts.jsonl' });
|
|
37
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
(0, vitest_1.it)('requires a .json or .jsonl file, so {home}/.ssh/* is not declarable', () => {
|
|
40
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.ssh/*' });
|
|
41
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
42
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.x/a.json' }).ok).toBe(true);
|
|
43
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.x/a.jsonl' }).ok).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
(0, vitest_1.it)('allows a glob only in the file name', () => {
|
|
46
|
+
// A '*' in a directory segment would walk the home directory.
|
|
47
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/*/sessions/a.jsonl' }).ok).toBe(false);
|
|
48
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.x/*.jsonl' }).ok).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
(0, vitest_1.it)('allows at most one glob', () => {
|
|
51
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.x/*-*.jsonl' }).ok).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
(0, vitest_1.it)('reports an unknown placeholder instead of looking up a literal directory', () => {
|
|
54
|
+
const parsed = (0, custom_agents_1.parseCustomAgent)({ ...good, transcript: '{home}/.x/{sessionid}/a.jsonl' });
|
|
55
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
56
|
+
(0, vitest_1.expect)(parsed.ok === false && parsed.error).toContain('{sessionid}');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.describe)('the name rules', () => {
|
|
60
|
+
(0, vitest_1.it)('rejects a name that would match half the process table', () => {
|
|
61
|
+
for (const name of ['', ' ', '*', '.*', 'my agent', 'bin/agent', 'a'.repeat(65)]) {
|
|
62
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, name }).ok, JSON.stringify(name)).toBe(false);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
(0, vitest_1.it)('accepts the characters real binaries use', () => {
|
|
66
|
+
for (const name of ['agy', 'my-agent', 'my_agent', 'agent.sh', 'claude2', 'c++agent']) {
|
|
67
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)({ ...good, name }).ok, name).toBe(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
(0, vitest_1.it)('rejects junk rather than throwing', () => {
|
|
72
|
+
for (const bad of [null, undefined, 42, 'str', [], {}]) {
|
|
73
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgent)(bad).ok).toBe(false);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
(0, vitest_1.describe)('parseCustomAgents', () => {
|
|
78
|
+
(0, vitest_1.it)('treats absent as none', () => {
|
|
79
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgents)(undefined)).toEqual({ ok: true, agents: [] });
|
|
80
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgents)(null)).toEqual({ ok: true, agents: [] });
|
|
81
|
+
});
|
|
82
|
+
(0, vitest_1.it)('reports the first bad entry rather than silently dropping it', () => {
|
|
83
|
+
// Silently dropping would leave the user with a config they can see and a
|
|
84
|
+
// session they cannot, and nothing connecting the two.
|
|
85
|
+
const parsed = (0, custom_agents_1.parseCustomAgents)([good, { ...good, name: 'x', kind: 'nope' }]);
|
|
86
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
(0, vitest_1.it)('rejects two entries claiming one name', () => {
|
|
89
|
+
const parsed = (0, custom_agents_1.parseCustomAgents)([good, { ...good, transcript: '{home}/.other/a.json' }]);
|
|
90
|
+
(0, vitest_1.expect)(parsed.ok).toBe(false);
|
|
91
|
+
(0, vitest_1.expect)(parsed.ok === false && parsed.error).toContain('duplicate');
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)('rejects a non-array', () => {
|
|
94
|
+
(0, vitest_1.expect)((0, custom_agents_1.parseCustomAgents)({ name: 'x' }).ok).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
(0, vitest_1.describe)('customAgentRule', () => {
|
|
98
|
+
const rule = (name) => (0, custom_agents_1.customAgentRule)({ ...good, name });
|
|
99
|
+
(0, vitest_1.it)('anchors on a path-segment boundary, like the built-in rules', () => {
|
|
100
|
+
(0, vitest_1.expect)(rule('sagent').test('sagent')).toBe(true);
|
|
101
|
+
(0, vitest_1.expect)(rule('sagent').test('sagent --resume')).toBe(true);
|
|
102
|
+
(0, vitest_1.expect)(rule('sagent').test('/usr/local/bin/sagent')).toBe(true);
|
|
103
|
+
// A bare space before the name is neither start-of-string nor a slash.
|
|
104
|
+
(0, vitest_1.expect)(rule('sagent').test('vim sagent')).toBe(false);
|
|
105
|
+
(0, vitest_1.expect)(rule('sagent').test('sagentry')).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
(0, vitest_1.it)('escapes the name, so a declaration cannot inject a pattern', () => {
|
|
108
|
+
// '.' must match a literal dot and nothing else; without escaping,
|
|
109
|
+
// 'a.y' would match 'agy' — and a name of '.*' would match everything.
|
|
110
|
+
(0, vitest_1.expect)(rule('a.y').test('a.y')).toBe(true);
|
|
111
|
+
(0, vitest_1.expect)(rule('a.y').test('agy')).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
(0, vitest_1.describe)('expandTranscript', () => {
|
|
115
|
+
(0, vitest_1.it)('fills the placeholders it is given', () => {
|
|
116
|
+
(0, vitest_1.expect)((0, custom_agents_1.expandTranscript)('{home}/.x/{pid}/a.jsonl', { home: '/h', pid: 42 }))
|
|
117
|
+
.toBe('/h/.x/42/a.jsonl');
|
|
118
|
+
(0, vitest_1.expect)((0, custom_agents_1.expandTranscript)('{home}/.x/{tty}.json', { home: '/h', tty: '/dev/ttys1' }))
|
|
119
|
+
.toBe('/h/.x//dev/ttys1.json');
|
|
120
|
+
});
|
|
121
|
+
(0, vitest_1.it)('returns null when a placeholder it needs has no value', () => {
|
|
122
|
+
// Otherwise '{cwd}' is looked up as a literal directory of that name,
|
|
123
|
+
// which never matches and reads as "your declaration is wrong".
|
|
124
|
+
(0, vitest_1.expect)((0, custom_agents_1.expandTranscript)('{home}/.x/{cwd}/a.jsonl', { home: '/h' })).toBeNull();
|
|
125
|
+
(0, vitest_1.expect)((0, custom_agents_1.expandTranscript)('{home}/.x/a.jsonl', { home: '' })).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
(0, vitest_1.describe)('transcriptMatcher', () => {
|
|
129
|
+
(0, vitest_1.it)('matches an exact file name when there is no glob', () => {
|
|
130
|
+
const m = (0, custom_agents_1.transcriptMatcher)('/h/.x/chat.jsonl');
|
|
131
|
+
(0, vitest_1.expect)(m.dir).toBe('/h/.x');
|
|
132
|
+
(0, vitest_1.expect)(m.match('chat.jsonl')).toBe(true);
|
|
133
|
+
(0, vitest_1.expect)(m.match('other.jsonl')).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
(0, vitest_1.it)('expands a glob within the file name only', () => {
|
|
136
|
+
const m = (0, custom_agents_1.transcriptMatcher)('/h/.x/*.jsonl');
|
|
137
|
+
(0, vitest_1.expect)(m.dir).toBe('/h/.x');
|
|
138
|
+
(0, vitest_1.expect)(m.match('a.jsonl')).toBe(true);
|
|
139
|
+
(0, vitest_1.expect)(m.match('deep/a.jsonl')).toBe(false);
|
|
140
|
+
(0, vitest_1.expect)(m.match('a.json')).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
(0, vitest_1.it)('treats the non-glob part of the name literally', () => {
|
|
143
|
+
const m = (0, custom_agents_1.transcriptMatcher)('/h/.x/roll.out-*.jsonl');
|
|
144
|
+
(0, vitest_1.expect)(m.match('roll.out-1.jsonl')).toBe(true);
|
|
145
|
+
(0, vitest_1.expect)(m.match('rollXout-1.jsonl')).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.describe)('isAiKind', () => {
|
|
149
|
+
(0, vitest_1.it)('knows the four vendors with adapters', () => {
|
|
150
|
+
for (const k of ['claude', 'codex', 'grok', 'augment'])
|
|
151
|
+
(0, vitest_1.expect)((0, custom_agents_1.isAiKind)(k)).toBe(true);
|
|
152
|
+
for (const k of ['gemini', '', null, undefined, 7])
|
|
153
|
+
(0, vitest_1.expect)((0, custom_agents_1.isAiKind)(k)).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type { AiKind } from './kinds';
|
|
2
|
+
export type { CustomAgent, CustomAgentParse, TranscriptVars } from './custom-agents';
|
|
3
|
+
export { AI_KINDS, isAiKind, parseCustomAgent, parseCustomAgents, customAgentRule, expandTranscript, transcriptMatcher, TRANSCRIPT_VARS, } from './custom-agents';
|
|
2
4
|
export type { AiStatus, AiEvent, AiAdapter } from './events';
|
|
3
5
|
export { PREVIEW_LIMIT, preview } from './events';
|
|
4
6
|
export type { PermissionMode } from './permission';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.isSendable = exports.folderLabel = exports.shouldSendNotification = exports.isNotificationType = exports.parseTemplatePatch = exports.parseActions = exports.renderNotification = exports.categoryId = exports.MAX_ACTIONS = exports.BODY_MAX = exports.TITLE_MAX = exports.DEFAULT_TEMPLATES = exports.TEMPLATE_VARIABLES = exports.ACTION_KINDS = exports.NOTIFICATION_TYPES = exports.splitAttachments = exports.safeAttachmentName = exports.isImageName = exports.composeWithAttachments = exports.ATTACHMENT_MAX_FILES = exports.ATTACHMENT_MAX_BYTES = exports.ATTACHMENT_DIR = exports.promptMoveKeys = void 0;
|
|
3
|
+
exports.parseCapturePage = exports.MAX_CAPTURE_PAGE = exports.EMPTY_CAPTURE = exports.mergeEvents = exports.EMPTY_TRANSCRIPT = exports.resolveTmuxPrefs = exports.isValidTmuxColor = exports.isValidStatusTemplate = exports.compileStatusTemplate = exports.MAX_STATUS_TEMPLATE = exports.STATUS_TOKENS = exports.DEFAULT_TMUX_PREFS = exports.renderTerminalName = exports.factsFromSession = exports.TERMINAL_NAME_TOKENS = exports.DEFAULT_TERMINAL_TEMPLATE = exports.setFunctionKeysVisible = exports.getFunctionKeysVisible = exports.FUNCTION_KEYS_PREF = exports.shouldSubmitOnEnter = exports.COARSE_POINTER_QUERY = exports.rankCommands = exports.commandQuery = exports.toolAppearance = exports.toolClass = exports.shouldFollow = exports.PIN_THRESHOLD_PX = exports.readUserMessage = exports.dedent = exports.toolGroupStartsOpen = exports.groupEvents = exports.shouldAnnounceSession = exports.shouldNotifyNewAiSession = exports.shouldNotifyNewTerminal = exports.shouldNotifyPrompt = exports.shouldNotifyAi = exports.NOTIFY_COOLDOWN_MS = exports.REPLAY_LIMIT = exports.isPermissionMode = exports.PERMISSION_MODES = exports.preview = exports.PREVIEW_LIMIT = exports.TRANSCRIPT_VARS = exports.transcriptMatcher = exports.expandTranscript = exports.customAgentRule = exports.parseCustomAgents = exports.parseCustomAgent = exports.isAiKind = exports.AI_KINDS = void 0;
|
|
4
|
+
exports.isSendable = exports.folderLabel = exports.shouldSendNotification = exports.isNotificationType = exports.parseTemplatePatch = exports.parseActions = exports.renderNotification = exports.categoryId = exports.MAX_ACTIONS = exports.BODY_MAX = exports.TITLE_MAX = exports.DEFAULT_TEMPLATES = exports.TEMPLATE_VARIABLES = exports.ACTION_KINDS = exports.NOTIFICATION_TYPES = exports.splitAttachments = exports.safeAttachmentName = exports.isImageName = exports.composeWithAttachments = exports.ATTACHMENT_MAX_FILES = exports.ATTACHMENT_MAX_BYTES = exports.ATTACHMENT_DIR = exports.promptMoveKeys = exports.promptKeys = exports.isSafeKeyList = exports.isFreeTextOption = exports.freeTextRow = exports.detectPanePrompt = exports.PROMPT_KEYS = exports.thinkingLabel = exports.thinkingDuration = void 0;
|
|
5
|
+
var custom_agents_1 = require("./custom-agents");
|
|
6
|
+
Object.defineProperty(exports, "AI_KINDS", { enumerable: true, get: function () { return custom_agents_1.AI_KINDS; } });
|
|
7
|
+
Object.defineProperty(exports, "isAiKind", { enumerable: true, get: function () { return custom_agents_1.isAiKind; } });
|
|
8
|
+
Object.defineProperty(exports, "parseCustomAgent", { enumerable: true, get: function () { return custom_agents_1.parseCustomAgent; } });
|
|
9
|
+
Object.defineProperty(exports, "parseCustomAgents", { enumerable: true, get: function () { return custom_agents_1.parseCustomAgents; } });
|
|
10
|
+
Object.defineProperty(exports, "customAgentRule", { enumerable: true, get: function () { return custom_agents_1.customAgentRule; } });
|
|
11
|
+
Object.defineProperty(exports, "expandTranscript", { enumerable: true, get: function () { return custom_agents_1.expandTranscript; } });
|
|
12
|
+
Object.defineProperty(exports, "transcriptMatcher", { enumerable: true, get: function () { return custom_agents_1.transcriptMatcher; } });
|
|
13
|
+
Object.defineProperty(exports, "TRANSCRIPT_VARS", { enumerable: true, get: function () { return custom_agents_1.TRANSCRIPT_VARS; } });
|
|
5
14
|
var events_1 = require("./events");
|
|
6
15
|
Object.defineProperty(exports, "PREVIEW_LIMIT", { enumerable: true, get: function () { return events_1.PREVIEW_LIMIT; } });
|
|
7
16
|
Object.defineProperty(exports, "preview", { enumerable: true, get: function () { return events_1.preview; } });
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type AiKind = 'claude' | 'codex' | 'grok';
|
|
1
|
+
export type AiKind = 'claude' | 'codex' | 'grok' | 'augment';
|
|
2
2
|
//# sourceMappingURL=kinds.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pane-prompt.d.ts","sourceRoot":"","sources":["../src/pane-prompt.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"pane-prompt.d.ts","sourceRoot":"","sources":["../src/pane-prompt.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;CACnB;AAgID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAuChE;AAED,yEAAyE;AACzE,eAAO,MAAM,WAAW,sGAAuG,CAAC;AAChI,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAGjD"}
|
|
@@ -95,9 +95,18 @@ function numberedMenu(lines, bottom) {
|
|
|
95
95
|
for (let i = bottom; i >= 0; i--) {
|
|
96
96
|
const raw = lines[i];
|
|
97
97
|
if (!raw.trim()) {
|
|
98
|
-
if (opts.length
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
if (!opts.length && !hints.length)
|
|
99
|
+
continue;
|
|
100
|
+
// A blank line BETWEEN options is normal and must not end the scan.
|
|
101
|
+
// Claude Code separates its trailing rows ("Type something.", "Chat
|
|
102
|
+
// about this") from the numbered choices with one, and breaking here
|
|
103
|
+
// collected a single option, failed the two-option minimum, and returned
|
|
104
|
+
// null — so a question sitting on screen was reported as no prompt at
|
|
105
|
+
// all and the session read "working" while the agent waited. Measured
|
|
106
|
+
// against a real pane on 2026-10-30.
|
|
107
|
+
if (i > 0 && NUMBERED.test(lines[i - 1]))
|
|
108
|
+
continue;
|
|
109
|
+
break;
|
|
101
110
|
}
|
|
102
111
|
if (SEPARATOR.test(raw.trim())) {
|
|
103
112
|
if (++separators > 2)
|
|
@@ -115,8 +124,14 @@ function numberedMenu(lines, bottom) {
|
|
|
115
124
|
}
|
|
116
125
|
// Deeper than an option's label: that option's explanation (it sits
|
|
117
126
|
// below its option, so it is seen first when scanning upwards).
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
//
|
|
128
|
+
// Keep only the first few for display, but keep SCANNING past the rest. A
|
|
129
|
+
// long explanation is not the top of the menu, and stopping at the fourth
|
|
130
|
+
// line of one truncated the menu to whatever had been collected below it.
|
|
131
|
+
// Claude Code routinely writes five- and six-line explanations.
|
|
132
|
+
if (raw.search(/\S/) >= Math.max(4, labelCol)) {
|
|
133
|
+
if (hints.length < 3)
|
|
134
|
+
hints.unshift(raw.trim());
|
|
120
135
|
continue;
|
|
121
136
|
}
|
|
122
137
|
break;
|
|
@@ -182,3 +182,78 @@ README.md package.json src
|
|
|
182
182
|
(0, vitest_1.expect)((0, pane_prompt_1.freeTextRow)((0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH))).toBe(0);
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
|
+
/**
|
|
186
|
+
* A real Claude Code question, captured 2026-10-30 from a pane whose session
|
|
187
|
+
* showed "Claude is working…" in the app while the terminal sat waiting on it.
|
|
188
|
+
*
|
|
189
|
+
* Two guards rejected it, and the symptom of either is the same: detectPanePrompt
|
|
190
|
+
* returns null, statusWithPrompt leaves the status as whatever transcript timing
|
|
191
|
+
* said, and a blocked agent reads as a busy one. Nothing is logged, because "no
|
|
192
|
+
* prompt" is indistinguishable from "no prompt yet".
|
|
193
|
+
*
|
|
194
|
+
* - a blank line BETWEEN options ended the upward scan, so only the last row
|
|
195
|
+
* was collected and the two-option minimum failed;
|
|
196
|
+
* - an explanation longer than three lines ended it too, truncating the menu
|
|
197
|
+
* to whatever had been collected below.
|
|
198
|
+
*/
|
|
199
|
+
(0, vitest_1.describe)('a menu with blank lines between options and long explanations', () => {
|
|
200
|
+
const pane = [
|
|
201
|
+
" That's the right test design — you can't test \"the runner refuses to",
|
|
202
|
+
' continue when an applied migration has been edited" against your',
|
|
203
|
+
' real dev database. The question is how to give it a cluster.',
|
|
204
|
+
'',
|
|
205
|
+
' □ Test cluster',
|
|
206
|
+
' The new migration tests need a throwaway Postgres cluster, and',
|
|
207
|
+
" macOS's default kernel limits are too small for `initdb` to",
|
|
208
|
+
' start one. How should I unblock it?',
|
|
209
|
+
'',
|
|
210
|
+
'❯ 1. Use Docker instead (Recommended)',
|
|
211
|
+
' Redo database-schema so the ephemeral-cluster harness launches a',
|
|
212
|
+
' disposable postgres:17-alpine container instead of calling',
|
|
213
|
+
' initdb. Slower per test (~2-3s startup), but it needs no kernel',
|
|
214
|
+
' change, works on any machine, and matches how the project already',
|
|
215
|
+
' runs its dev database. Costs one more redo round.',
|
|
216
|
+
' 2. Raise the kernel limits',
|
|
217
|
+
' Run sudo sysctl to raise kern.sysv.shmmax/shmall/shmseg on your',
|
|
218
|
+
' Mac so initdb works. Fastest path — no code change, possibly',
|
|
219
|
+
' unblocks your project right now. But it is a machine-wide change,',
|
|
220
|
+
' needs your password, reverts on reboot unless written to',
|
|
221
|
+
' /etc/sysctl.conf, and the same failure will reappear on any other',
|
|
222
|
+
' machine or CI runner.',
|
|
223
|
+
' 3. Both',
|
|
224
|
+
' Raise the limits now to get P4B moving today, and separately',
|
|
225
|
+
' record a task to move the harness onto Docker before P9',
|
|
226
|
+
' launch-readiness, so CI is not left with a host-specific',
|
|
227
|
+
' dependency.',
|
|
228
|
+
' 4. Type something.',
|
|
229
|
+
'',
|
|
230
|
+
' 5. Chat about this',
|
|
231
|
+
'',
|
|
232
|
+
'Enter to select · ↑/↓ to navigate · Esc to cancel',
|
|
233
|
+
].join('\n');
|
|
234
|
+
(0, vitest_1.it)('is detected at all', () => {
|
|
235
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(pane)).not.toBeNull();
|
|
236
|
+
});
|
|
237
|
+
(0, vitest_1.it)('collects every option, not just the ones below the blank line', () => {
|
|
238
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(pane);
|
|
239
|
+
(0, vitest_1.expect)(p.options.map((o) => o.n)).toEqual([1, 2, 3, 4, 5]);
|
|
240
|
+
(0, vitest_1.expect)(p.options[4].label).toBe('Chat about this');
|
|
241
|
+
});
|
|
242
|
+
(0, vitest_1.it)('keeps the cursor on the row the TUI has it on', () => {
|
|
243
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(pane);
|
|
244
|
+
(0, vitest_1.expect)(p.options.filter((o) => o.selected).map((o) => o.n)).toEqual([1]);
|
|
245
|
+
});
|
|
246
|
+
(0, vitest_1.it)('reads it as a question to answer, not an approval to grant', () => {
|
|
247
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(pane).approval).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
// The row that takes typed text rather than being an answer itself: picking
|
|
250
|
+
// it with Enter alone would answer with nothing.
|
|
251
|
+
(0, vitest_1.it)('still finds the free-text row behind the long explanations', () => {
|
|
252
|
+
(0, vitest_1.expect)((0, pane_prompt_1.freeTextRow)((0, pane_prompt_1.detectPanePrompt)(pane))).toBe(4);
|
|
253
|
+
});
|
|
254
|
+
(0, vitest_1.it)('keeps an explanation with each option that has one', () => {
|
|
255
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(pane);
|
|
256
|
+
(0, vitest_1.expect)(p.options[0].hint).toBeTruthy();
|
|
257
|
+
(0, vitest_1.expect)(p.options[3].hint).toBeUndefined();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":1,"sessions":{"e864cc5c-dad7-49c4-8666-6d7fb2297d2a":{"updatedAt":1793336945946,"files":{"/Volumes/SandeepSSD/www/github/dialout/packages/devdash-shared/src/index.ts":{"editCount":1,"findings":[]},"/Volumes/SandeepSSD/www/github/dialout/packages/devdash-agent/src/config.ts":{"editCount":2,"findings":[]},"/Volumes/SandeepSSD/www/github/dialout/packages/devdash-agent/src/ai-session-detector.ts":{"editCount":3,"findings":[]},"/Volumes/SandeepSSD/www/github/dialout/packages/devdash-agent/src/ai-transcript-locator.ts":{"editCount":4,"findings":[]}}}}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"entries": {
|
|
4
|
+
"79a93d4a2f8f50b95f852280616242fee1855dc99a3c75211917f55e72e95fae": {
|
|
5
|
+
"last_emitted_at_ms": 1793336950903,
|
|
6
|
+
"message": "Use parallel execution for independent tasks. Use run_in_background for long operations (npm install, builds, tests)."
|
|
7
|
+
},
|
|
8
|
+
"445ed27a3872b681d98190bae61ccb84954e1bc4e140df5370be958dee776b3a": {
|
|
9
|
+
"last_emitted_at_ms": 1793336884091,
|
|
10
|
+
"message": "Verify changes work after editing. Test functionality before marking complete."
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"updated_at": "2026-10-30T05:09:10.903Z"
|
|
14
|
+
}
|