@aligndottech/cli 0.34.1 → 0.36.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/README.md +2 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/decisions/index.d.ts.map +1 -1
- package/dist/commands/decisions/index.js +9 -1
- package/dist/commands/decisions/index.js.map +1 -1
- package/dist/commands/import/sessions.d.ts +28 -0
- package/dist/commands/import/sessions.d.ts.map +1 -0
- package/dist/commands/import/sessions.js +215 -0
- package/dist/commands/import/sessions.js.map +1 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +2 -0
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/push.d.ts +25 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +98 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/ratify.d.ts +23 -0
- package/dist/commands/ratify.d.ts.map +1 -0
- package/dist/commands/ratify.js +51 -0
- package/dist/commands/ratify.js.map +1 -0
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/commands/why.js +4 -1
- package/dist/commands/why.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/answer-render.d.ts +2 -0
- package/dist/lib/answer-render.d.ts.map +1 -0
- package/dist/lib/answer-render.js +24 -0
- package/dist/lib/answer-render.js.map +1 -0
- package/dist/lib/capture-sources.d.ts +4 -0
- package/dist/lib/capture-sources.d.ts.map +1 -1
- package/dist/lib/capture-sources.js +1 -0
- package/dist/lib/capture-sources.js.map +1 -1
- package/dist/lib/decider-kind.d.ts +37 -0
- package/dist/lib/decider-kind.d.ts.map +1 -0
- package/dist/lib/decider-kind.js +41 -0
- package/dist/lib/decider-kind.js.map +1 -0
- package/dist/lib/decision-render.d.ts +35 -0
- package/dist/lib/decision-render.d.ts.map +1 -0
- package/dist/lib/decision-render.js +149 -0
- package/dist/lib/decision-render.js.map +1 -0
- package/dist/lib/decisions-context.d.ts +7 -0
- package/dist/lib/decisions-context.d.ts.map +1 -1
- package/dist/lib/decisions-context.js +29 -6
- package/dist/lib/decisions-context.js.map +1 -1
- package/dist/lib/gateway-client.d.ts +32 -0
- package/dist/lib/gateway-client.d.ts.map +1 -1
- package/dist/lib/gateway-client.js +10 -0
- package/dist/lib/gateway-client.js.map +1 -1
- package/dist/lib/git.d.ts +18 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +31 -0
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/import-defaults.d.ts +1 -0
- package/dist/lib/import-defaults.d.ts.map +1 -1
- package/dist/lib/import-defaults.js +1 -0
- package/dist/lib/import-defaults.js.map +1 -1
- package/dist/lib/local-db.d.ts +56 -1
- package/dist/lib/local-db.d.ts.map +1 -1
- package/dist/lib/local-db.js +104 -7
- package/dist/lib/local-db.js.map +1 -1
- package/dist/lib/local-gateway-client.d.ts +50 -0
- package/dist/lib/local-gateway-client.d.ts.map +1 -1
- package/dist/lib/local-gateway-client.js +94 -5
- package/dist/lib/local-gateway-client.js.map +1 -1
- package/dist/lib/local-llm.d.ts +66 -1
- package/dist/lib/local-llm.d.ts.map +1 -1
- package/dist/lib/local-llm.js +167 -12
- package/dist/lib/local-llm.js.map +1 -1
- package/dist/lib/local-relationship-classifier.d.ts +18 -0
- package/dist/lib/local-relationship-classifier.d.ts.map +1 -1
- package/dist/lib/local-relationship-classifier.js +32 -6
- package/dist/lib/local-relationship-classifier.js.map +1 -1
- package/dist/lib/personal-import.d.ts +26 -0
- package/dist/lib/personal-import.d.ts.map +1 -1
- package/dist/lib/personal-import.js +43 -0
- package/dist/lib/personal-import.js.map +1 -1
- package/dist/lib/sessions/adapters/claude-code.d.ts +4 -0
- package/dist/lib/sessions/adapters/claude-code.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/claude-code.js +93 -0
- package/dist/lib/sessions/adapters/claude-code.js.map +1 -0
- package/dist/lib/sessions/adapters/codex.d.ts +6 -0
- package/dist/lib/sessions/adapters/codex.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/codex.js +91 -0
- package/dist/lib/sessions/adapters/codex.js.map +1 -0
- package/dist/lib/sessions/adapters/cursor.d.ts +17 -0
- package/dist/lib/sessions/adapters/cursor.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/cursor.js +31 -0
- package/dist/lib/sessions/adapters/cursor.js.map +1 -0
- package/dist/lib/sessions/adapters/gemini-cli.d.ts +15 -0
- package/dist/lib/sessions/adapters/gemini-cli.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/gemini-cli.js +29 -0
- package/dist/lib/sessions/adapters/gemini-cli.js.map +1 -0
- package/dist/lib/sessions/adapters/opencode.d.ts +4 -0
- package/dist/lib/sessions/adapters/opencode.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/opencode.js +100 -0
- package/dist/lib/sessions/adapters/opencode.js.map +1 -0
- package/dist/lib/sessions/adapters/pi.d.ts +4 -0
- package/dist/lib/sessions/adapters/pi.d.ts.map +1 -0
- package/dist/lib/sessions/adapters/pi.js +102 -0
- package/dist/lib/sessions/adapters/pi.js.map +1 -0
- package/dist/lib/sessions/confirm-freetext.d.ts +43 -0
- package/dist/lib/sessions/confirm-freetext.d.ts.map +1 -0
- package/dist/lib/sessions/confirm-freetext.js +82 -0
- package/dist/lib/sessions/confirm-freetext.js.map +1 -0
- package/dist/lib/sessions/extract-freetext.d.ts +26 -0
- package/dist/lib/sessions/extract-freetext.d.ts.map +1 -0
- package/dist/lib/sessions/extract-freetext.js +53 -0
- package/dist/lib/sessions/extract-freetext.js.map +1 -0
- package/dist/lib/sessions/extract-structured.d.ts +21 -0
- package/dist/lib/sessions/extract-structured.d.ts.map +1 -0
- package/dist/lib/sessions/extract-structured.js +72 -0
- package/dist/lib/sessions/extract-structured.js.map +1 -0
- package/dist/lib/sessions/fs-scan.d.ts +10 -0
- package/dist/lib/sessions/fs-scan.d.ts.map +1 -0
- package/dist/lib/sessions/fs-scan.js +33 -0
- package/dist/lib/sessions/fs-scan.js.map +1 -0
- package/dist/lib/sessions/registry.d.ts +11 -0
- package/dist/lib/sessions/registry.d.ts.map +1 -0
- package/dist/lib/sessions/registry.js +22 -0
- package/dist/lib/sessions/registry.js.map +1 -0
- package/dist/lib/sessions/source-url.d.ts +10 -0
- package/dist/lib/sessions/source-url.d.ts.map +1 -0
- package/dist/lib/sessions/source-url.js +20 -0
- package/dist/lib/sessions/source-url.js.map +1 -0
- package/dist/lib/sessions/types.d.ts +62 -0
- package/dist/lib/sessions/types.d.ts.map +1 -0
- package/dist/lib/sessions/types.js +19 -0
- package/dist/lib/sessions/types.js.map +1 -0
- package/dist/lib/token-estimate.d.ts +17 -0
- package/dist/lib/token-estimate.d.ts.map +1 -0
- package/dist/lib/token-estimate.js +24 -0
- package/dist/lib/token-estimate.js.map +1 -0
- package/package.json +10 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { findFilesRecursive, sortByMtimeDesc } from '../fs-scan.js';
|
|
5
|
+
function parseLine(raw) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(raw);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Only pi's first line (`type: "session"`) carries cwd - unlike Claude Code, later lines
|
|
14
|
+
* do not repeat it, so this only needs to look at the first parseable line. */
|
|
15
|
+
function peekCwd(filePath) {
|
|
16
|
+
const firstLine = readFileSync(filePath, 'utf8').split('\n').find(l => l.trim());
|
|
17
|
+
if (!firstLine)
|
|
18
|
+
return null;
|
|
19
|
+
return parseLine(firstLine)?.cwd ?? null;
|
|
20
|
+
}
|
|
21
|
+
export function locateSessionFilesUnder(sessionsRoot, cwd) {
|
|
22
|
+
const files = findFilesRecursive(sessionsRoot, /\.jsonl$/);
|
|
23
|
+
return sortByMtimeDesc(files.filter(f => peekCwd(f) === cwd));
|
|
24
|
+
}
|
|
25
|
+
function extractText(content) {
|
|
26
|
+
let text = '';
|
|
27
|
+
const toolCalls = [];
|
|
28
|
+
if (typeof content === 'string') {
|
|
29
|
+
text = content;
|
|
30
|
+
}
|
|
31
|
+
else if (Array.isArray(content)) {
|
|
32
|
+
for (const block of content) {
|
|
33
|
+
if (!block || typeof block !== 'object')
|
|
34
|
+
continue;
|
|
35
|
+
const b = block;
|
|
36
|
+
if (b['type'] === 'text' && typeof b['text'] === 'string') {
|
|
37
|
+
text += (text ? '\n' : '') + b['text'];
|
|
38
|
+
}
|
|
39
|
+
else if (b['type'] === 'toolCall' && typeof b['id'] === 'string' && typeof b['name'] === 'string') {
|
|
40
|
+
toolCalls.push({ id: b['id'], name: b['name'], arguments: b['arguments'] });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { text, toolCalls };
|
|
45
|
+
}
|
|
46
|
+
/** pi's toolResult text lives in a `content: [{type:'text', text}]` array on its own
|
|
47
|
+
* message, same shape as a user/assistant message's text blocks. */
|
|
48
|
+
function extractResultText(content) {
|
|
49
|
+
return extractText(content).text;
|
|
50
|
+
}
|
|
51
|
+
function parsePiSession(filePath) {
|
|
52
|
+
const text = readFileSync(filePath, 'utf8');
|
|
53
|
+
const turns = [];
|
|
54
|
+
let sessionId = null;
|
|
55
|
+
let cwd = null;
|
|
56
|
+
// toolResult lines are their own top-level pi message (role: 'toolResult'), not nested
|
|
57
|
+
// inside the next user/assistant turn the way Claude Code embeds a tool_result block -
|
|
58
|
+
// so a pending result is carried forward and merged into whichever real turn comes next.
|
|
59
|
+
let pendingResults = {};
|
|
60
|
+
for (const rawLine of text.split('\n')) {
|
|
61
|
+
if (!rawLine.trim())
|
|
62
|
+
continue;
|
|
63
|
+
const line = parseLine(rawLine);
|
|
64
|
+
if (!line)
|
|
65
|
+
continue;
|
|
66
|
+
if (line.type === 'session') {
|
|
67
|
+
sessionId ??= line.id ?? null;
|
|
68
|
+
cwd ??= line.cwd ?? null;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (line.type !== 'message')
|
|
72
|
+
continue;
|
|
73
|
+
const role = line.message?.role;
|
|
74
|
+
if (role === 'toolResult' && typeof line.message?.toolCallId === 'string') {
|
|
75
|
+
pendingResults[line.message.toolCallId] = extractResultText(line.message.content);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (role !== 'user' && role !== 'assistant')
|
|
79
|
+
continue;
|
|
80
|
+
const { text: turnText, toolCalls } = extractText(line.message?.content);
|
|
81
|
+
turns.push({ role, text: turnText, toolCalls, toolResults: pendingResults, timestamp: line.timestamp ?? null });
|
|
82
|
+
pendingResults = {};
|
|
83
|
+
}
|
|
84
|
+
// A pi transcript ending immediately after a toolResult (nothing to attach it to yet) -
|
|
85
|
+
// fold it onto the last turn rather than dropping it.
|
|
86
|
+
if (Object.keys(pendingResults).length > 0 && turns.length > 0) {
|
|
87
|
+
Object.assign(turns[turns.length - 1].toolResults, pendingResults);
|
|
88
|
+
}
|
|
89
|
+
if (!sessionId || turns.length === 0)
|
|
90
|
+
return null;
|
|
91
|
+
return { agent: 'pi', sessionId, cwd, turns };
|
|
92
|
+
}
|
|
93
|
+
export const piAdapter = {
|
|
94
|
+
agent: 'pi',
|
|
95
|
+
fixtureVerified: true,
|
|
96
|
+
locateSessionFiles(cwd) {
|
|
97
|
+
const root = join(homedir(), '.pi', 'agent', 'sessions');
|
|
98
|
+
return locateSessionFilesUnder(root, cwd);
|
|
99
|
+
},
|
|
100
|
+
parseSession: parsePiSession,
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=pi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../../src/lib/sessions/adapters/pi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type LlmFailure } from '../local-llm.js';
|
|
2
|
+
import type { AgentName } from './types.js';
|
|
3
|
+
import type { RawFreeTextCandidate } from './extract-freetext.js';
|
|
4
|
+
/** A raw heuristic hit the model reviewed and stood behind - the only shape that may reach
|
|
5
|
+
* the confirm-each UX (align decision ALI-409: "only adjudicated results assert"). */
|
|
6
|
+
export interface ConfirmedFreeTextDecision {
|
|
7
|
+
agent: AgentName;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
messageId: string;
|
|
10
|
+
/** Verbatim from the raw candidate - never replaced by the model's title. See
|
|
11
|
+
* extract-freetext.ts's RawFreeTextCandidate.humanText docstring for why. */
|
|
12
|
+
humanText: string;
|
|
13
|
+
/** A short label for the confirm-each review list, written by the model FROM humanText and
|
|
14
|
+
* contextText - never itself stored as the claim's own words. */
|
|
15
|
+
title: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
timestamp: string | null;
|
|
18
|
+
}
|
|
19
|
+
/** Coarse on purpose, same reasoning as local-relationship-classifier.ts's
|
|
20
|
+
* ClassifierFailureReason: the finer detail travels on `failure` (provider, model, detail). */
|
|
21
|
+
export type ConfirmFailureReason = 'no_llm_key' | 'confirm_error' | 'confirm_unparseable' | 'unvetted_local_model';
|
|
22
|
+
export type ConfirmOutcome =
|
|
23
|
+
/** decision: null means the model looked and said the heuristic misfired - nothing was
|
|
24
|
+
* actually decided here, so nothing is returned to review. */
|
|
25
|
+
{
|
|
26
|
+
ok: true;
|
|
27
|
+
decision: ConfirmedFreeTextDecision | null;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: ConfirmFailureReason;
|
|
31
|
+
failure?: LlmFailure;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A per-reason hint, not one collapsed "no LLM configured" line - align-stack's own
|
|
35
|
+
* local-gateway-client.ts already documents why collapsing this union is wrong (ALI-420: "an
|
|
36
|
+
* unvetted local model gets its own remedy... nonsense to someone already running one") and
|
|
37
|
+
* ALI-692 for provider_stopped naming the model that actually failed. Only `no_llm_key` means
|
|
38
|
+
* nothing is configured; the other three all require a provider that DID answer or a model
|
|
39
|
+
* that IS installed, so telling that caller to "set a key or run Ollama" is actively wrong.
|
|
40
|
+
*/
|
|
41
|
+
export declare function describeConfirmFailure(reason: ConfirmFailureReason, failure?: LlmFailure): string;
|
|
42
|
+
export declare function confirmFreeTextCandidate(candidate: RawFreeTextCandidate): Promise<ConfirmOutcome>;
|
|
43
|
+
//# sourceMappingURL=confirm-freetext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-freetext.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/confirm-freetext.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;uFACuF;AACvF,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;kFAC8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB;sEACkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;gGACgG;AAChG,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,eAAe,GAAG,qBAAqB,GAAG,sBAAsB,CAAC;AAEnH,MAAM,MAAM,cAAc;AACxB;+DAC+D;AAC7D;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAA;CAAE,GACxD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,OAAO,CAAC,EAAE,UAAU,CAAA;CAAE,CAAC;AAoCtE;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAajG;AAED,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA4BvG"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { DETERMINISTIC_TEMPERATURE } from '@aligndottech/connector-core';
|
|
2
|
+
import { callChatDetailed, CLASSIFIER_MAX_TOKENS, hasConfiguredProvider, noProviderHintInline, RECOMMENDED_OLLAMA_PULL, resolveMaxTokens, } from '../local-llm.js';
|
|
3
|
+
const SYSTEM_PROMPT = 'You review one turn from a coding-agent session transcript to decide whether the HUMAN ' +
|
|
4
|
+
'stated a decision or redirect mid-session (e.g. "let\'s use X instead", "go with Y", ' +
|
|
5
|
+
'"decided to switch to Z"). CONTEXT is the agent\'s reply and is background only - it tells ' +
|
|
6
|
+
'you what was actually resolved, but the thing being reviewed is the human\'s turn, not the ' +
|
|
7
|
+
'agent\'s. Respond ONLY with compact JSON: ' +
|
|
8
|
+
'{"isDecision": boolean, "title": short string naming what was decided, "confidence": number 0-1}. ' +
|
|
9
|
+
'Set isDecision to false when HUMAN_TEXT is a question, a bug report, a command, or anything ' +
|
|
10
|
+
'else that is not actually a decision or redirect.';
|
|
11
|
+
function buildUserPrompt(candidate) {
|
|
12
|
+
return `HUMAN_TEXT: ${candidate.humanText}\n\nCONTEXT (the agent's reply, background only): ${candidate.contextText ?? '(none)'}`;
|
|
13
|
+
}
|
|
14
|
+
function parseConfirmation(text) {
|
|
15
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
16
|
+
if (!match)
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const obj = JSON.parse(match[0]);
|
|
20
|
+
if (typeof obj.isDecision !== 'boolean')
|
|
21
|
+
return null;
|
|
22
|
+
const confidence = typeof obj.confidence === 'number' ? Math.max(0, Math.min(1, obj.confidence)) : 0.5;
|
|
23
|
+
const title = typeof obj.title === 'string' ? obj.title.trim() : '';
|
|
24
|
+
return { isDecision: obj.isDecision, title, confidence };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A per-reason hint, not one collapsed "no LLM configured" line - align-stack's own
|
|
32
|
+
* local-gateway-client.ts already documents why collapsing this union is wrong (ALI-420: "an
|
|
33
|
+
* unvetted local model gets its own remedy... nonsense to someone already running one") and
|
|
34
|
+
* ALI-692 for provider_stopped naming the model that actually failed. Only `no_llm_key` means
|
|
35
|
+
* nothing is configured; the other three all require a provider that DID answer or a model
|
|
36
|
+
* that IS installed, so telling that caller to "set a key or run Ollama" is actively wrong.
|
|
37
|
+
*/
|
|
38
|
+
export function describeConfirmFailure(reason, failure) {
|
|
39
|
+
switch (reason) {
|
|
40
|
+
case 'no_llm_key':
|
|
41
|
+
return noProviderHintInline('these can be confirmed');
|
|
42
|
+
case 'unvetted_local_model':
|
|
43
|
+
return ` Ollama is running, but no recognised model is installed: \`ollama pull ${RECOMMENDED_OLLAMA_PULL}\`, or set ALIGN_OLLAMA_MODEL to name your own.`;
|
|
44
|
+
case 'confirm_error':
|
|
45
|
+
return failure?.kind === 'provider_stopped'
|
|
46
|
+
? ` ${failure.model} (${failure.provider}) returned an unusable response (${failure.detail}) - try again.`
|
|
47
|
+
: ' The configured provider did not answer usably - try again.';
|
|
48
|
+
case 'confirm_unparseable':
|
|
49
|
+
return ' The model replied without usable JSON - this is usually transient; try again.';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function confirmFreeTextCandidate(candidate) {
|
|
53
|
+
const maxTokens = resolveMaxTokens('ALIGN_CLASSIFIER_MAX_TOKENS', CLASSIFIER_MAX_TOKENS);
|
|
54
|
+
const result = await callChatDetailed(SYSTEM_PROMPT, buildUserPrompt(candidate), {
|
|
55
|
+
temperature: DETERMINISTIC_TEMPERATURE,
|
|
56
|
+
maxTokens,
|
|
57
|
+
});
|
|
58
|
+
if (!result.ok) {
|
|
59
|
+
const reason = result.failure.kind === 'unrecognised_local_models' ? 'unvetted_local_model'
|
|
60
|
+
: result.failure.kind === 'provider_stopped' ? 'confirm_error'
|
|
61
|
+
: hasConfiguredProvider() ? 'confirm_error' : 'no_llm_key';
|
|
62
|
+
return { ok: false, reason, failure: result.failure };
|
|
63
|
+
}
|
|
64
|
+
const parsed = parseConfirmation(result.text);
|
|
65
|
+
if (!parsed)
|
|
66
|
+
return { ok: false, reason: 'confirm_unparseable' };
|
|
67
|
+
if (!parsed.isDecision)
|
|
68
|
+
return { ok: true, decision: null };
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
decision: {
|
|
72
|
+
agent: candidate.agent,
|
|
73
|
+
sessionId: candidate.sessionId,
|
|
74
|
+
messageId: candidate.messageId,
|
|
75
|
+
humanText: candidate.humanText,
|
|
76
|
+
title: parsed.title || candidate.humanText,
|
|
77
|
+
confidence: parsed.confidence,
|
|
78
|
+
timestamp: candidate.timestamp,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=confirm-freetext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-freetext.js","sourceRoot":"","sources":["../../../src/lib/sessions/confirm-freetext.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentName, CanonicalSession } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ALI-809 Pass B, heuristic half: a HUMAN turn whose own words redirect or settle on an
|
|
4
|
+
* approach mid-session. Everything downstream (confirm-freetext.ts) treats this as a proposal
|
|
5
|
+
* only - "heuristic proposes, LLM adjudicates, only adjudicated results assert" is the same
|
|
6
|
+
* shape as decision_links' writer split (align decision ALI-409), applied here to candidates
|
|
7
|
+
* instead of edges.
|
|
8
|
+
*/
|
|
9
|
+
export interface RawFreeTextCandidate {
|
|
10
|
+
agent: AgentName;
|
|
11
|
+
sessionId: string;
|
|
12
|
+
/** `turn-<index>` into the parsed session's turns array - stable for one session file, and
|
|
13
|
+
* used only as an object-reference-free identity in the source_url; nothing here needs it
|
|
14
|
+
* to survive a re-parse of a DIFFERENT file. */
|
|
15
|
+
messageId: string;
|
|
16
|
+
/** The human's own words, verbatim - the only decider-authored content in this candidate. */
|
|
17
|
+
humanText: string;
|
|
18
|
+
/** The assistant turn immediately after, when there is one - CONTEXT for the LLM confirmation
|
|
19
|
+
* pass to read, never itself scanned for the trigger phrase and never the source of the
|
|
20
|
+
* claim (the same "don't let bot content pass as the human's own statement" principle as
|
|
21
|
+
* align decision ALI-538, applied to a different pipeline). */
|
|
22
|
+
contextText: string | null;
|
|
23
|
+
timestamp: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function findFreeTextCandidates(session: CanonicalSession): RawFreeTextCandidate[];
|
|
26
|
+
//# sourceMappingURL=extract-freetext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-freetext.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/extract-freetext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB;;qDAEiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB;;;oEAGgE;IAChE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAsCD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,oBAAoB,EAAE,CAgBxF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phrases a human types when redirecting or settling on an approach mid-session. Calibrated
|
|
3
|
+
* against four REAL captured sessions (fixtures/sessions/), not invented - see
|
|
4
|
+
* extract-freetext.test.ts's end-to-end block for which fixture drove which pattern. A miss
|
|
5
|
+
* here is a missed candidate, not a wrong one (the LLM pass only ever narrows further), so
|
|
6
|
+
* staying narrow and growing this as more real sessions are captured beats guessing at
|
|
7
|
+
* phrasings nobody has actually said.
|
|
8
|
+
*/
|
|
9
|
+
const DECISION_PHRASES = [
|
|
10
|
+
/\binstead of\b/i,
|
|
11
|
+
/\blet'?s (go with|use|do|switch to|just use|just go with)\b/i,
|
|
12
|
+
/\bdecid(e|ed|ing|ision)\b/i,
|
|
13
|
+
/\bgo with\b/i,
|
|
14
|
+
/\bswitch(ed|ing)? to\b/i,
|
|
15
|
+
];
|
|
16
|
+
function isDecisionShaped(text) {
|
|
17
|
+
return DECISION_PHRASES.some(p => p.test(text));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The assistant's reply to a human's redirect is rarely one turn: a commentary aside, a tool
|
|
21
|
+
* call, and the actual resolution routinely land as separate turns in the canonical shape
|
|
22
|
+
* (confirmed against the real codex and opencode fixtures - a tool-call turn carries text: ''
|
|
23
|
+
* and sits between the commentary and the resolution in both). Walks every CONSECUTIVE
|
|
24
|
+
* assistant turn starting at `from`, joining the non-empty ones, and stops at the next human
|
|
25
|
+
* turn or the end of the session - never crossing into a later human turn's own reply.
|
|
26
|
+
*/
|
|
27
|
+
function collectAssistantContext(turns, from) {
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (let i = from; i < turns.length && turns[i].role === 'assistant'; i++) {
|
|
30
|
+
if (turns[i].text)
|
|
31
|
+
parts.push(turns[i].text);
|
|
32
|
+
}
|
|
33
|
+
return parts.length ? parts.join('\n') : null;
|
|
34
|
+
}
|
|
35
|
+
export function findFreeTextCandidates(session) {
|
|
36
|
+
const candidates = [];
|
|
37
|
+
session.turns.forEach((turn, i) => {
|
|
38
|
+
// toolCalls-only turns carry text: '' (types.ts), which no phrase can match - no special
|
|
39
|
+
// case needed beyond the phrase test itself.
|
|
40
|
+
if (turn.role !== 'user' || !isDecisionShaped(turn.text))
|
|
41
|
+
return;
|
|
42
|
+
candidates.push({
|
|
43
|
+
agent: session.agent,
|
|
44
|
+
sessionId: session.sessionId,
|
|
45
|
+
messageId: `turn-${i}`,
|
|
46
|
+
humanText: turn.text,
|
|
47
|
+
contextText: collectAssistantContext(session.turns, i + 1),
|
|
48
|
+
timestamp: turn.timestamp,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
return candidates;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=extract-freetext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-freetext.js","sourceRoot":"","sources":["../../../src/lib/sessions/extract-freetext.ts"],"names":[],"mappings":"AA0BA;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAsB;IAC1C,iBAAiB;IACjB,8DAA8D;IAC9D,4BAA4B;IAC5B,cAAc;IACd,yBAAyB;CAC1B,CAAC;AAEF,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,uBAAuB,CAAC,KAAgC,EAAE,IAAY;IAC7E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1E,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAyB;IAC9D,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAChC,yFAAyF;QACzF,6CAA6C;QAC7C,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO;QACjE,UAAU,CAAC,IAAI,CAAC;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,SAAS,EAAE,QAAQ,CAAC,EAAE;YACtB,SAAS,EAAE,IAAI,CAAC,IAAI;YACpB,WAAW,EAAE,uBAAuB,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;YAC1D,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentName, CanonicalSession } from './types.js';
|
|
2
|
+
export interface SessionDecisionCandidate {
|
|
3
|
+
agent: AgentName;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
/** The structured tool call's own id - unique within the session, and the value used to
|
|
6
|
+
* build the source_url's messageId segment. */
|
|
7
|
+
messageId: string;
|
|
8
|
+
question: string;
|
|
9
|
+
header: string | null;
|
|
10
|
+
options: Array<{
|
|
11
|
+
label: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}>;
|
|
14
|
+
/** The answer, verbatim - either a listed option's label or free text (Claude Code's
|
|
15
|
+
* "Other"). Never null here: a candidate with no resolvable answer is not returned at
|
|
16
|
+
* all (see extractAskUserQuestion). */
|
|
17
|
+
chosenLabel: string;
|
|
18
|
+
timestamp: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function extractStructuredDecisions(session: CanonicalSession): SessionDecisionCandidate[];
|
|
21
|
+
//# sourceMappingURL=extract-structured.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-structured.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/extract-structured.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAe,MAAM,YAAY,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB;oDACgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD;;4CAEwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAUD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,GAAG,wBAAwB,EAAE,CAEhG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const PRODUCERS = {
|
|
2
|
+
'claude-code': extractAskUserQuestion,
|
|
3
|
+
};
|
|
4
|
+
export function extractStructuredDecisions(session) {
|
|
5
|
+
return PRODUCERS[session.agent]?.(session) ?? [];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Claude Code's AskUserQuestion tool_result is free text, not structured JSON - the tool
|
|
9
|
+
* itself renders it that way (`"<question>"="<answer>". <trailing sentence>`). Anchoring on
|
|
10
|
+
* the question's own text (echoed verbatim by the tool) rather than splitting on commas
|
|
11
|
+
* keeps this correct even when an answer itself contains a comma or a quote.
|
|
12
|
+
*/
|
|
13
|
+
function parseAnsweredText(resultText, question) {
|
|
14
|
+
const marker = `"${question}"=`;
|
|
15
|
+
const idx = resultText.indexOf(marker);
|
|
16
|
+
if (idx === -1)
|
|
17
|
+
return null;
|
|
18
|
+
const after = resultText.slice(idx + marker.length);
|
|
19
|
+
if (!after.startsWith('"'))
|
|
20
|
+
return null;
|
|
21
|
+
const body = after.slice(1);
|
|
22
|
+
// The two real trailing phrasings this tool emits, whichever fires depends on whether one
|
|
23
|
+
// question was asked or several. A quote not followed by either is inside the answer.
|
|
24
|
+
const suffixMatch = body.search(/"\.\s*(Read the answers|You can now continue)/);
|
|
25
|
+
if (suffixMatch !== -1)
|
|
26
|
+
return body.slice(0, suffixMatch);
|
|
27
|
+
// Last resort (e.g. this is the final question in a multi-question call, with nothing
|
|
28
|
+
// trailing but the closing quote): take up to the last quote in the string.
|
|
29
|
+
const lastQuote = body.lastIndexOf('"');
|
|
30
|
+
return lastQuote === -1 ? null : body.slice(0, lastQuote);
|
|
31
|
+
}
|
|
32
|
+
function extractAskUserQuestion(session) {
|
|
33
|
+
const candidates = [];
|
|
34
|
+
for (const turn of session.turns) {
|
|
35
|
+
for (const call of turn.toolCalls) {
|
|
36
|
+
if (call.name !== 'AskUserQuestion')
|
|
37
|
+
continue;
|
|
38
|
+
const input = call.arguments;
|
|
39
|
+
const q = input?.questions?.[0];
|
|
40
|
+
if (!q)
|
|
41
|
+
continue;
|
|
42
|
+
const resultText = findToolResult(session.turns, call.id);
|
|
43
|
+
if (resultText === null)
|
|
44
|
+
continue;
|
|
45
|
+
const chosenLabel = parseAnsweredText(resultText, q.question);
|
|
46
|
+
// A rejection ("the tool use was rejected...") or any other unrecognised result shape
|
|
47
|
+
// has no `"<question>"=` marker, so this is also where a decline is filtered out -
|
|
48
|
+
// nothing was decided, so nothing is a candidate.
|
|
49
|
+
if (chosenLabel === null)
|
|
50
|
+
continue;
|
|
51
|
+
candidates.push({
|
|
52
|
+
agent: session.agent,
|
|
53
|
+
sessionId: session.sessionId,
|
|
54
|
+
messageId: call.id,
|
|
55
|
+
question: q.question,
|
|
56
|
+
header: q.header ?? null,
|
|
57
|
+
options: q.options.map(o => ({ label: o.label, ...(o.description !== undefined ? { description: o.description } : {}) })),
|
|
58
|
+
chosenLabel,
|
|
59
|
+
timestamp: turn.timestamp,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return candidates;
|
|
64
|
+
}
|
|
65
|
+
function findToolResult(turns, toolCallId) {
|
|
66
|
+
for (const turn of turns) {
|
|
67
|
+
if (toolCallId in turn.toolResults)
|
|
68
|
+
return turn.toolResults[toolCallId];
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=extract-structured.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-structured.js","sourceRoot":"","sources":["../../../src/lib/sessions/extract-structured.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every file under `root` (recursively) whose full path matches `pattern`. A missing or
|
|
3
|
+
* unreadable root is "this agent has never run here" - the normal case, not an error - so
|
|
4
|
+
* it returns [] rather than throwing. Every adapter's locateSessionFiles builds on this.
|
|
5
|
+
*/
|
|
6
|
+
export declare function findFilesRecursive(root: string, pattern: RegExp): string[];
|
|
7
|
+
/** Newest-first by mtime - agent-agnostic, so it works whether or not a format's own
|
|
8
|
+
* embedded timestamp was parseable. */
|
|
9
|
+
export declare function sortByMtimeDesc(files: string[]): string[];
|
|
10
|
+
//# sourceMappingURL=fs-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-scan.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/fs-scan.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB1E;AAED;wCACwC;AACxC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAEzD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Every file under `root` (recursively) whose full path matches `pattern`. A missing or
|
|
5
|
+
* unreadable root is "this agent has never run here" - the normal case, not an error - so
|
|
6
|
+
* it returns [] rather than throwing. Every adapter's locateSessionFiles builds on this.
|
|
7
|
+
*/
|
|
8
|
+
export function findFilesRecursive(root, pattern) {
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const found = [];
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const full = join(root, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
found.push(...findFilesRecursive(full, pattern));
|
|
21
|
+
}
|
|
22
|
+
else if (pattern.test(full)) {
|
|
23
|
+
found.push(full);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
/** Newest-first by mtime - agent-agnostic, so it works whether or not a format's own
|
|
29
|
+
* embedded timestamp was parseable. */
|
|
30
|
+
export function sortByMtimeDesc(files) {
|
|
31
|
+
return [...files].sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=fs-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-scan.js","sourceRoot":"","sources":["../../../src/lib/sessions/fs-scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,OAAe;IAC9D,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;wCACwC;AACxC,MAAM,UAAU,eAAe,CAAC,KAAe;IAC7C,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SessionAdapter } from './types.js';
|
|
2
|
+
export declare const ADAPTERS: SessionAdapter[];
|
|
3
|
+
export interface DetectedAgent {
|
|
4
|
+
adapter: SessionAdapter;
|
|
5
|
+
files: string[];
|
|
6
|
+
}
|
|
7
|
+
/** `align import sessions`' auto-detect: every agent that has actually left session data
|
|
8
|
+
* for this project. An agent with zero files is "never run here" - the normal case for
|
|
9
|
+
* five of six adapters on any one machine - so it is silently excluded, not reported. */
|
|
10
|
+
export declare function detectAgents(cwd: string, adapters?: SessionAdapter[]): DetectedAgent[];
|
|
11
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,QAAQ,EAAE,cAAc,EAEpC,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;0FAE0F;AAC1F,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,cAAc,EAAa,GAAG,aAAa,EAAE,CAOhG"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { claudeCodeAdapter } from './adapters/claude-code.js';
|
|
2
|
+
import { codexAdapter } from './adapters/codex.js';
|
|
3
|
+
import { cursorAdapter } from './adapters/cursor.js';
|
|
4
|
+
import { geminiCliAdapter } from './adapters/gemini-cli.js';
|
|
5
|
+
import { opencodeAdapter } from './adapters/opencode.js';
|
|
6
|
+
import { piAdapter } from './adapters/pi.js';
|
|
7
|
+
export const ADAPTERS = [
|
|
8
|
+
claudeCodeAdapter, codexAdapter, cursorAdapter, geminiCliAdapter, opencodeAdapter, piAdapter,
|
|
9
|
+
];
|
|
10
|
+
/** `align import sessions`' auto-detect: every agent that has actually left session data
|
|
11
|
+
* for this project. An agent with zero files is "never run here" - the normal case for
|
|
12
|
+
* five of six adapters on any one machine - so it is silently excluded, not reported. */
|
|
13
|
+
export function detectAgents(cwd, adapters = ADAPTERS) {
|
|
14
|
+
const found = [];
|
|
15
|
+
for (const adapter of adapters) {
|
|
16
|
+
const files = adapter.locateSessionFiles(cwd);
|
|
17
|
+
if (files.length > 0)
|
|
18
|
+
found.push({ adapter, files });
|
|
19
|
+
}
|
|
20
|
+
return found;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/lib/sessions/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG7C,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,iBAAiB,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS;CAC7F,CAAC;AAOF;;0FAE0F;AAC1F,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,WAA6B,QAAQ;IAC7E,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentName } from './types.js';
|
|
2
|
+
/** ALI-808 shared contract: the agent name lives in the scheme, `platform` stays the single
|
|
3
|
+
* value `agent-session` everywhere else. */
|
|
4
|
+
export declare function buildSessionSourceUrl(agent: AgentName, sessionId: string, messageId: string): string;
|
|
5
|
+
export declare function parseSessionSourceUrl(url: string): {
|
|
6
|
+
agent: AgentName;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
messageId: string;
|
|
9
|
+
} | null;
|
|
10
|
+
//# sourceMappingURL=source-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-url.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C;6CAC6C;AAC7C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWpH"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const AGENTS = ['claude-code', 'codex', 'cursor', 'gemini-cli', 'opencode', 'pi'];
|
|
2
|
+
/** ALI-808 shared contract: the agent name lives in the scheme, `platform` stays the single
|
|
3
|
+
* value `agent-session` everywhere else. */
|
|
4
|
+
export function buildSessionSourceUrl(agent, sessionId, messageId) {
|
|
5
|
+
return `${agent}-session://${sessionId}/${messageId}`;
|
|
6
|
+
}
|
|
7
|
+
export function parseSessionSourceUrl(url) {
|
|
8
|
+
for (const agent of AGENTS) {
|
|
9
|
+
const prefix = `${agent}-session://`;
|
|
10
|
+
if (url.startsWith(prefix)) {
|
|
11
|
+
const rest = url.slice(prefix.length);
|
|
12
|
+
const slash = rest.indexOf('/');
|
|
13
|
+
if (slash === -1)
|
|
14
|
+
return null;
|
|
15
|
+
return { agent, sessionId: rest.slice(0, slash), messageId: rest.slice(slash + 1) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=source-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-url.js","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAExG;6CAC6C;AAC7C,MAAM,UAAU,qBAAqB,CAAC,KAAgB,EAAE,SAAiB,EAAE,SAAiB;IAC1F,OAAO,GAAG,KAAK,cAAc,SAAS,IAAI,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,CAAC;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|