@moxxy/sdk 0.44.0 → 0.45.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/agent-account-limit.d.ts +41 -0
- package/dist/agent-account-limit.d.ts.map +1 -0
- package/dist/agent-account-limit.js +64 -0
- package/dist/agent-account-limit.js.map +1 -0
- package/dist/agent-computer-use.d.ts +10 -0
- package/dist/agent-computer-use.d.ts.map +1 -0
- package/dist/agent-computer-use.js +24 -0
- package/dist/agent-computer-use.js.map +1 -0
- package/dist/agent-context-window.d.ts +22 -0
- package/dist/agent-context-window.d.ts.map +1 -0
- package/dist/agent-context-window.js +40 -0
- package/dist/agent-context-window.js.map +1 -0
- package/dist/agent-no-reply.d.ts +11 -0
- package/dist/agent-no-reply.d.ts.map +1 -0
- package/dist/agent-no-reply.js +13 -0
- package/dist/agent-no-reply.js.map +1 -0
- package/dist/agent-step.d.ts +14 -0
- package/dist/agent-step.d.ts.map +1 -0
- package/dist/agent-step.js +26 -0
- package/dist/agent-step.js.map +1 -0
- package/dist/agent-work.d.ts +34 -0
- package/dist/agent-work.d.ts.map +1 -0
- package/dist/agent-work.js +80 -0
- package/dist/agent-work.js.map +1 -0
- package/dist/attachment-payload.d.ts +1 -1
- package/dist/attachment-payload.js +1 -1
- package/dist/auto-approve.d.ts +9 -0
- package/dist/auto-approve.d.ts.map +1 -1
- package/dist/auto-approve.js +18 -0
- package/dist/auto-approve.js.map +1 -1
- package/dist/channel-auth.d.ts +3 -3
- package/dist/channel-auth.d.ts.map +1 -1
- package/dist/channel-auth.js +1 -1
- package/dist/channel.d.ts +1 -1
- package/dist/client-session.d.ts +9 -5
- package/dist/client-session.d.ts.map +1 -1
- package/dist/computer-control.d.ts +11 -11
- package/dist/computer-control.d.ts.map +1 -1
- package/dist/computer-control.js +1 -1
- package/dist/computer-control.js.map +1 -1
- package/dist/conversation-history.d.ts +50 -0
- package/dist/conversation-history.d.ts.map +1 -0
- package/dist/conversation-history.js +227 -0
- package/dist/conversation-history.js.map +1 -0
- package/dist/cron.d.ts +58 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +320 -0
- package/dist/cron.js.map +1 -0
- package/dist/define.d.ts +2 -1
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +1 -0
- package/dist/define.js.map +1 -1
- package/dist/event-store.d.ts +1 -1
- package/dist/event-store.d.ts.map +1 -1
- package/dist/event-store.js +1 -0
- package/dist/event-store.js.map +1 -1
- package/dist/events.d.ts +17 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/file-diff.d.ts +31 -0
- package/dist/file-diff.d.ts.map +1 -0
- package/dist/file-diff.js +196 -0
- package/dist/file-diff.js.map +1 -0
- package/dist/index.d.ts +22 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/mode/agent-step-loop.d.ts +13 -0
- package/dist/mode/agent-step-loop.d.ts.map +1 -0
- package/dist/mode/agent-step-loop.js +578 -0
- package/dist/mode/agent-step-loop.js.map +1 -0
- package/dist/mode/checkpoint-gate.d.ts +25 -0
- package/dist/mode/checkpoint-gate.d.ts.map +1 -0
- package/dist/mode/checkpoint-gate.js +150 -0
- package/dist/mode/checkpoint-gate.js.map +1 -0
- package/dist/mode/checkpoint.d.ts +6 -0
- package/dist/mode/checkpoint.d.ts.map +1 -1
- package/dist/mode/collect-stream.d.ts +2 -0
- package/dist/mode/collect-stream.d.ts.map +1 -1
- package/dist/mode/collect-stream.js +1 -1
- package/dist/mode/collect-stream.js.map +1 -1
- package/dist/mode/project-messages.d.ts.map +1 -1
- package/dist/mode/project-messages.js +9 -67
- package/dist/mode/project-messages.js.map +1 -1
- package/dist/mode/react-loop-options.d.ts +113 -0
- package/dist/mode/react-loop-options.d.ts.map +1 -0
- package/dist/mode/react-loop-options.js +2 -0
- package/dist/mode/react-loop-options.js.map +1 -0
- package/dist/mode/react-loop.d.ts +2 -99
- package/dist/mode/react-loop.d.ts.map +1 -1
- package/dist/mode/react-loop.js +7 -145
- package/dist/mode/react-loop.js.map +1 -1
- package/dist/mode.d.ts +19 -7
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/npm-registry.d.ts +22 -0
- package/dist/npm-registry.d.ts.map +1 -0
- package/dist/npm-registry.js +54 -0
- package/dist/npm-registry.js.map +1 -0
- package/dist/permission.d.ts +31 -0
- package/dist/permission.d.ts.map +1 -1
- package/dist/process-invocation.d.ts +2 -0
- package/dist/process-invocation.d.ts.map +1 -1
- package/dist/process-invocation.js +2 -2
- package/dist/process-invocation.js.map +1 -1
- package/dist/process-tree.d.ts +9 -0
- package/dist/process-tree.d.ts.map +1 -0
- package/dist/process-tree.js +40 -0
- package/dist/process-tree.js.map +1 -0
- package/dist/provider-login-bridge.js +1 -1
- package/dist/provider-login-bridge.js.map +1 -1
- package/dist/provider.d.ts +408 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/question-draft.d.ts +19 -0
- package/dist/question-draft.d.ts.map +1 -0
- package/dist/question-draft.js +30 -0
- package/dist/question-draft.js.map +1 -0
- package/dist/question.d.ts +204 -0
- package/dist/question.d.ts.map +1 -0
- package/dist/question.js +141 -0
- package/dist/question.js.map +1 -0
- package/dist/questions.d.ts +7 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +7 -0
- package/dist/questions.js.map +1 -0
- package/dist/resolvers.d.ts +14 -1
- package/dist/resolvers.d.ts.map +1 -1
- package/dist/resolvers.js +33 -21
- package/dist/resolvers.js.map +1 -1
- package/dist/schemas.d.ts +53 -53
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -2
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/dist/session-like.d.ts +19 -1
- package/dist/session-like.d.ts.map +1 -1
- package/dist/starting-model.d.ts +12 -0
- package/dist/starting-model.d.ts.map +1 -0
- package/dist/starting-model.js +9 -0
- package/dist/starting-model.js.map +1 -0
- package/dist/tool-dispatch.d.ts.map +1 -1
- package/dist/tool-dispatch.js +10 -4
- package/dist/tool-dispatch.js.map +1 -1
- package/dist/tool-display.d.ts +79 -3
- package/dist/tool-display.d.ts.map +1 -1
- package/dist/tool-display.js +117 -3
- package/dist/tool-display.js.map +1 -1
- package/dist/tool-modes.d.ts +1 -1
- package/dist/tool-modes.d.ts.map +1 -1
- package/dist/tool-modes.js +8 -2
- package/dist/tool-modes.js.map +1 -1
- package/dist/tool-output.d.ts +15 -0
- package/dist/tool-output.d.ts.map +1 -0
- package/dist/tool-output.js +39 -0
- package/dist/tool-output.js.map +1 -0
- package/dist/tool.d.ts +14 -1
- package/dist/tool.d.ts.map +1 -1
- package/dist/tunnel.d.ts +3 -3
- package/dist/tunnel.d.ts.map +1 -1
- package/dist/tunnel.js +2 -2
- package/dist/workflow-approval.d.ts +9 -9
- package/dist/workflow-approval.d.ts.map +1 -1
- package/dist/workflow-approval.js +1 -1
- package/dist/workflow-approval.js.map +1 -1
- package/package.json +33 -5
- package/src/agent-account-limit.test.ts +87 -0
- package/src/agent-account-limit.ts +89 -0
- package/src/agent-computer-use.test.ts +26 -0
- package/src/agent-computer-use.ts +29 -0
- package/src/agent-context-window.ts +51 -0
- package/src/agent-no-reply.ts +16 -0
- package/src/agent-step.ts +26 -0
- package/src/agent-work.ts +102 -0
- package/src/attachment-payload.ts +1 -1
- package/src/auto-approve.test.ts +13 -1
- package/src/auto-approve.ts +19 -0
- package/src/channel-auth.ts +3 -3
- package/src/channel.ts +1 -1
- package/src/client-session.ts +9 -2
- package/src/computer-control.ts +1 -1
- package/src/conversation-history.test.ts +184 -0
- package/src/conversation-history.ts +272 -0
- package/src/cron.test.ts +280 -0
- package/src/cron.ts +355 -0
- package/src/define.test.ts +1 -1
- package/src/define.ts +3 -1
- package/src/event-store.ts +2 -1
- package/src/events.ts +17 -1
- package/src/file-diff.test.ts +103 -0
- package/src/file-diff.ts +208 -0
- package/src/index.ts +121 -1
- package/src/json-file-store.test.ts +1 -1
- package/src/loop-helpers.test.ts +15 -0
- package/src/mode/agent-step-loop.ts +645 -0
- package/src/mode/checkpoint-gate.ts +198 -0
- package/src/mode/checkpoint.ts +6 -0
- package/src/mode/collect-stream.ts +1 -1
- package/src/mode/project-messages.test.ts +11 -0
- package/src/mode/project-messages.ts +8 -61
- package/src/mode/react-loop-options.ts +124 -0
- package/src/mode/react-loop.ts +10 -296
- package/src/mode.ts +19 -7
- package/src/npm-registry.test.ts +95 -0
- package/src/npm-registry.ts +57 -0
- package/src/permission.ts +30 -0
- package/src/process-invocation.test.ts +25 -0
- package/src/process-invocation.ts +8 -3
- package/src/process-tree.ts +37 -0
- package/src/provider-login-bridge.ts +1 -1
- package/src/provider-schema-constraints.test.ts +1 -1
- package/src/provider-utils.test.ts +1 -1
- package/src/provider.ts +346 -0
- package/src/question-draft.test.ts +57 -0
- package/src/question-draft.ts +47 -0
- package/src/question.test.ts +130 -0
- package/src/question.ts +229 -0
- package/src/questions.ts +6 -0
- package/src/resolvers.test.ts +84 -1
- package/src/resolvers.ts +40 -27
- package/src/schemas.test.ts +5 -0
- package/src/schemas.ts +3 -2
- package/src/server.ts +3 -0
- package/src/session-like.ts +19 -1
- package/src/skill-mentions.test.ts +1 -1
- package/src/starting-model.test.ts +19 -0
- package/src/starting-model.ts +11 -0
- package/src/token-efficiency.test.ts +1 -1
- package/src/tool-dispatch.ts +10 -4
- package/src/tool-display.test.ts +119 -0
- package/src/tool-display.ts +162 -4
- package/src/tool-gating.test.ts +1 -1
- package/src/tool-icon.test.ts +1 -1
- package/src/tool-modes.test.ts +18 -1
- package/src/tool-modes.ts +6 -2
- package/src/tool-output.test.ts +46 -0
- package/src/tool-output.ts +46 -0
- package/src/tool.ts +14 -1
- package/src/tunnel.ts +3 -3
- package/src/workflow-approval.ts +1 -1
package/src/events.ts
CHANGED
|
@@ -74,11 +74,19 @@ export interface UserPromptAttachment {
|
|
|
74
74
|
* `kind: 'voice'` marks the voice conversation (GPT-Live) held while an agent
|
|
75
75
|
* turn ran and recorded after it as one transcript: context for later turns,
|
|
76
76
|
* not a request, and shown as one collapsed block.
|
|
77
|
+
*
|
|
78
|
+
* `kind: 'agent'` opens a turn the vendor's agent of an agent connection
|
|
79
|
+
* started by itself (Claude after a task it ran in the background finished),
|
|
80
|
+
* or one Moxxy opened to show work the agent left running ending (Codex's
|
|
81
|
+
* helper or background command); `name` says what it took up. Nothing is
|
|
82
|
+
* sent to the agent for it.
|
|
77
83
|
*/
|
|
78
84
|
export interface TriggerOrigin {
|
|
79
|
-
readonly kind: 'webhook' | 'schedule' | 'workflow' | 'checkpoint' | 'voice';
|
|
85
|
+
readonly kind: 'webhook' | 'schedule' | 'workflow' | 'checkpoint' | 'voice' | 'agent';
|
|
80
86
|
/** The trigger's name (webhook/schedule/workflow/checkpoint), for the marker label. */
|
|
81
87
|
readonly name: string;
|
|
88
|
+
/** `agent`: the background work whose end the agent took up (`AgentBackgroundWork.id`), when it says. */
|
|
89
|
+
readonly workId?: string;
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
export interface UserPromptEvent extends EventBase {
|
|
@@ -147,6 +155,8 @@ export interface ToolCallRequestedEvent extends EventBase {
|
|
|
147
155
|
readonly name: string;
|
|
148
156
|
readonly input: unknown;
|
|
149
157
|
readonly skillContext?: SkillId;
|
|
158
|
+
/** Set when the provider's own agent ran the tool (an agent connection), not Moxxy. */
|
|
159
|
+
readonly executedBy?: 'provider';
|
|
150
160
|
}
|
|
151
161
|
|
|
152
162
|
export interface ToolCallApprovedEvent extends EventBase {
|
|
@@ -309,6 +319,12 @@ export interface ProviderResponseEvent extends EventBase {
|
|
|
309
319
|
readonly outputTokens?: number;
|
|
310
320
|
readonly cacheReadTokens?: number;
|
|
311
321
|
readonly cacheCreationTokens?: number;
|
|
322
|
+
/**
|
|
323
|
+
* How many tokens the model's context holds after this response, when the usage
|
|
324
|
+
* above is not that: an agent connection reports one response per turn whose usage
|
|
325
|
+
* adds up every call the agent made.
|
|
326
|
+
*/
|
|
327
|
+
readonly contextTokens?: number;
|
|
312
328
|
readonly timing?: ProviderCallTiming;
|
|
313
329
|
}
|
|
314
330
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** A file change shown as a diff, whether Moxxy made it or an agent reported its own. */
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { diffPathIn, fileDiffFromPatch, fileDiffFromText, parseUnifiedDiff } from './file-diff.js';
|
|
4
|
+
|
|
5
|
+
describe('a diff from the text before and after', () => {
|
|
6
|
+
it('shows the changed lines with two lines of context and their numbers', () => {
|
|
7
|
+
const d = fileDiffFromText({ path: 'a.ts', mode: 'update', before: 'a\nb\nc\nd\ne\nf\ng', after: 'a\nb\nc\nX\ne\nf\ng' });
|
|
8
|
+
|
|
9
|
+
expect(d).toMatchObject({ kind: 'file-diff', path: 'a.ts', mode: 'update', added: 1, removed: 1 });
|
|
10
|
+
expect(d.hunks).toHaveLength(1);
|
|
11
|
+
expect(d.hunks[0]?.lines).toEqual([
|
|
12
|
+
{ kind: 'context', text: 'b', oldNo: 2, newNo: 2 },
|
|
13
|
+
{ kind: 'context', text: 'c', oldNo: 3, newNo: 3 },
|
|
14
|
+
{ kind: 'del', text: 'd', oldNo: 4 },
|
|
15
|
+
{ kind: 'add', text: 'X', newNo: 4 },
|
|
16
|
+
{ kind: 'context', text: 'e', oldNo: 5, newNo: 5 },
|
|
17
|
+
{ kind: 'context', text: 'f', oldNo: 6, newNo: 6 },
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('shows a new file as all added', () => {
|
|
22
|
+
const d = fileDiffFromText({ path: 'new.ts', mode: 'create', before: '', after: 'one\ntwo\n' });
|
|
23
|
+
|
|
24
|
+
expect(d).toMatchObject({ mode: 'create', added: 2, removed: 0 });
|
|
25
|
+
expect(d.hunks[0]?.lines.map((line) => line.kind)).toEqual(['add', 'add']);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('a diff from patch hunks an agent reports', () => {
|
|
30
|
+
it('numbers the lines of each hunk from where it starts', () => {
|
|
31
|
+
const d = fileDiffFromPatch({
|
|
32
|
+
path: 'src/db.ts',
|
|
33
|
+
mode: 'update',
|
|
34
|
+
hunks: [{ oldStart: 10, newStart: 10, lines: [' const host = "x";', '-const port = 5432;', '+const port = 5433;', '+const pool = 4;'] }],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(d).toMatchObject({ added: 2, removed: 1 });
|
|
38
|
+
expect(d.hunks).toEqual([
|
|
39
|
+
{
|
|
40
|
+
oldStart: 10,
|
|
41
|
+
oldLines: 2,
|
|
42
|
+
newStart: 10,
|
|
43
|
+
newLines: 3,
|
|
44
|
+
lines: [
|
|
45
|
+
{ kind: 'context', text: 'const host = "x";', oldNo: 10, newNo: 10 },
|
|
46
|
+
{ kind: 'del', text: 'const port = 5432;', oldNo: 11 },
|
|
47
|
+
{ kind: 'add', text: 'const port = 5433;', newNo: 11 },
|
|
48
|
+
{ kind: 'add', text: 'const pool = 4;', newNo: 12 },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('skips the "no newline at end of file" marker', () => {
|
|
55
|
+
const d = fileDiffFromPatch({ path: 'a', mode: 'update', hunks: [{ oldStart: 1, newStart: 1, lines: ['-a', '\', '+b'] }] });
|
|
56
|
+
|
|
57
|
+
expect(d.hunks[0]?.lines.map((line) => line.text)).toEqual(['a', 'b']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('caps what it shows on a huge patch but counts every line', () => {
|
|
61
|
+
const lines = Array.from({ length: 1_000 }, (_, i) => `+line ${i}`);
|
|
62
|
+
const d = fileDiffFromPatch({ path: 'big', mode: 'create', hunks: [{ oldStart: 0, newStart: 1, lines }] });
|
|
63
|
+
|
|
64
|
+
expect(d.added).toBe(1_000);
|
|
65
|
+
expect(d.truncated).toBe(true);
|
|
66
|
+
expect(d.hunks[0]?.lines.length).toBeLessThan(1_000);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('reading a unified diff', () => {
|
|
71
|
+
it('reads each hunk and leaves out the file headers', () => {
|
|
72
|
+
const text = '--- a/x.ts\n+++ b/x.ts\n@@ -1 +1,2 @@\n-old\n+new\n+more\n@@ -20,3 +21,3 @@ function f() {\n a\n-b\n+c\n d\n';
|
|
73
|
+
|
|
74
|
+
expect(parseUnifiedDiff(text)).toEqual([
|
|
75
|
+
{ oldStart: 1, newStart: 1, lines: ['-old', '+new', '+more'] },
|
|
76
|
+
{ oldStart: 20, newStart: 21, lines: [' a', '-b', '+c', ' d'] },
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('finds no hunks in text that is not a diff', () => {
|
|
81
|
+
expect(parseUnifiedDiff('just some file content')).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('the path a diff shows', () => {
|
|
86
|
+
it('is relative to the project folder when the file is inside it', () => {
|
|
87
|
+
expect(diffPathIn('/repo', '/repo/src/a.ts')).toBe('src/a.ts');
|
|
88
|
+
expect(diffPathIn('C:\\repo', 'C:\\repo\\src\\a.ts')).toBe('src\\a.ts');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('stays as given outside the project folder', () => {
|
|
92
|
+
expect(diffPathIn('/repo', '/repository/a.ts')).toBe('/repository/a.ts');
|
|
93
|
+
expect(diffPathIn('/repo', 'src/a.ts')).toBe('src/a.ts');
|
|
94
|
+
});
|
|
95
|
+
it('ignores trailing separators on the project folder, in time linear in its length', () => {
|
|
96
|
+
expect(diffPathIn('/repo//', '/repo/src/a.ts')).toBe('src/a.ts');
|
|
97
|
+
expect(diffPathIn('C:\\repo\\', 'C:\\repo\\a.ts')).toBe('a.ts');
|
|
98
|
+
|
|
99
|
+
const started = performance.now();
|
|
100
|
+
diffPathIn(`${'/'.repeat(100_000)}x`, '/a.ts');
|
|
101
|
+
expect(performance.now() - started).toBeLessThan(500);
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/file-diff.ts
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { DiffHunk, DiffLine, FileDiffDisplay } from './tool-display.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* File changes as `file-diff` displays, from what is at hand: the text before
|
|
5
|
+
* and after (Moxxy's Write/Edit, OpenCode), or patch hunks the agent already
|
|
6
|
+
* made (Claude's structured patch, Codex's unified diff). Only the changed
|
|
7
|
+
* slices are kept, so a display stays small whatever the file's size.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** A hunk as patches write it: lines prefixed with ' ', '+' or '-'. */
|
|
11
|
+
export interface PatchHunk {
|
|
12
|
+
readonly oldStart: number;
|
|
13
|
+
readonly newStart: number;
|
|
14
|
+
readonly lines: ReadonlyArray<string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface FileDiffTarget {
|
|
18
|
+
/** As the diff shows it; see `diffPathIn`. */
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly mode: 'create' | 'update';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Context lines kept around each change. */
|
|
24
|
+
const CONTEXT = 2;
|
|
25
|
+
/** Diff lines shown at most; the counts still cover every line. */
|
|
26
|
+
const MAX_DIFF_LINES = 400;
|
|
27
|
+
/** Above this combined size (~2 MB) only the counts are given. */
|
|
28
|
+
const MAX_DIFF_BYTES = 2_000_000;
|
|
29
|
+
/** LCS table cell budget; above it the changed middle is one replace block. */
|
|
30
|
+
const LCS_CELL_BUDGET = 1_000_000;
|
|
31
|
+
|
|
32
|
+
type Change = { readonly kind: DiffLine['kind']; readonly text: string };
|
|
33
|
+
|
|
34
|
+
export function fileDiffFromText(args: FileDiffTarget & { readonly before: string; readonly after: string }): FileDiffDisplay {
|
|
35
|
+
const before = toLines(args.before);
|
|
36
|
+
const after = toLines(args.after);
|
|
37
|
+
if (args.before.length + args.after.length > MAX_DIFF_BYTES) {
|
|
38
|
+
return { kind: 'file-diff', path: args.path, mode: args.mode, added: after.length, removed: before.length, hunks: [], truncated: true };
|
|
39
|
+
}
|
|
40
|
+
const lines = numberLines(1, 1, changesBetween(before, after));
|
|
41
|
+
const { hunks, truncated } = hunksAround(lines);
|
|
42
|
+
return display(args, lines, hunks, truncated);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function fileDiffFromPatch(args: FileDiffTarget & { readonly hunks: ReadonlyArray<PatchHunk> }): FileDiffDisplay {
|
|
46
|
+
const all: DiffLine[] = [];
|
|
47
|
+
const hunks: DiffHunk[] = [];
|
|
48
|
+
let shown = 0;
|
|
49
|
+
let truncated = false;
|
|
50
|
+
for (const hunk of args.hunks) {
|
|
51
|
+
const lines = numberLines(hunk.oldStart, hunk.newStart, hunk.lines.flatMap(patchChange));
|
|
52
|
+
all.push(...lines);
|
|
53
|
+
if (truncated) continue;
|
|
54
|
+
const kept = lines.slice(0, MAX_DIFF_LINES - shown);
|
|
55
|
+
shown += kept.length;
|
|
56
|
+
if (kept.length < lines.length) truncated = true;
|
|
57
|
+
if (kept.length > 0) hunks.push(hunkOf(kept));
|
|
58
|
+
}
|
|
59
|
+
return display(args, all, hunks, truncated);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The hunks of a unified diff; file headers and anything outside a hunk are left out. */
|
|
63
|
+
export function parseUnifiedDiff(text: string): PatchHunk[] {
|
|
64
|
+
const hunks: PatchHunk[] = [];
|
|
65
|
+
let current: { lines: string[]; oldLeft: number; newLeft: number } | undefined;
|
|
66
|
+
for (const line of text.split('\n')) {
|
|
67
|
+
if (current && (current.oldLeft > 0 || current.newLeft > 0 || line.startsWith('\\'))) {
|
|
68
|
+
// The header's counts say where the hunk ends; an empty line inside it is context written without its space.
|
|
69
|
+
if (line.startsWith('+')) current.newLeft -= 1;
|
|
70
|
+
else if (line.startsWith('-')) current.oldLeft -= 1;
|
|
71
|
+
else if (!line.startsWith('\\')) {
|
|
72
|
+
current.oldLeft -= 1;
|
|
73
|
+
current.newLeft -= 1;
|
|
74
|
+
}
|
|
75
|
+
current.lines.push(line);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const header = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
79
|
+
if (!header) continue;
|
|
80
|
+
current = { lines: [], oldLeft: Number(header[2] ?? 1), newLeft: Number(header[4] ?? 1) };
|
|
81
|
+
hunks.push({ oldStart: Number(header[1]), newStart: Number(header[3]), lines: current.lines });
|
|
82
|
+
}
|
|
83
|
+
return hunks;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `file` relative to the project folder `cwd` when inside it, else as given; either separator. */
|
|
87
|
+
export function diffPathIn(cwd: string, file: string): string {
|
|
88
|
+
let end = cwd.length;
|
|
89
|
+
while (end > 0 && (cwd[end - 1] === '/' || cwd[end - 1] === '\\')) end -= 1;
|
|
90
|
+
const root = cwd.slice(0, end);
|
|
91
|
+
if (root && file.startsWith(root) && (file[root.length] === '/' || file[root.length] === '\\')) return file.slice(root.length + 1);
|
|
92
|
+
return file;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function display(target: FileDiffTarget, lines: ReadonlyArray<DiffLine>, hunks: DiffHunk[], truncated: boolean): FileDiffDisplay {
|
|
96
|
+
return {
|
|
97
|
+
kind: 'file-diff',
|
|
98
|
+
path: target.path,
|
|
99
|
+
mode: target.mode,
|
|
100
|
+
added: lines.filter((line) => line.kind === 'add').length,
|
|
101
|
+
removed: lines.filter((line) => line.kind === 'del').length,
|
|
102
|
+
hunks,
|
|
103
|
+
...(truncated ? { truncated: true } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Lines of a text; a single trailing newline ends the last line, as in diffs. */
|
|
108
|
+
function toLines(text: string): string[] {
|
|
109
|
+
if (text.length === 0) return [];
|
|
110
|
+
return (text.endsWith('\n') ? text.slice(0, -1) : text).split('\n');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function patchChange(line: string): Change[] {
|
|
114
|
+
if (line.startsWith('+')) return [{ kind: 'add', text: line.slice(1) }];
|
|
115
|
+
if (line.startsWith('-')) return [{ kind: 'del', text: line.slice(1) }];
|
|
116
|
+
if (line.startsWith('\\')) return [];
|
|
117
|
+
return [{ kind: 'context', text: line.startsWith(' ') ? line.slice(1) : line }];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function numberLines(oldStart: number, newStart: number, changes: ReadonlyArray<Change>): DiffLine[] {
|
|
121
|
+
let oldNo = oldStart;
|
|
122
|
+
let newNo = newStart;
|
|
123
|
+
return changes.map((change) => {
|
|
124
|
+
if (change.kind === 'add') return { kind: 'add', text: change.text, newNo: newNo++ };
|
|
125
|
+
if (change.kind === 'del') return { kind: 'del', text: change.text, oldNo: oldNo++ };
|
|
126
|
+
return { kind: 'context', text: change.text, oldNo: oldNo++, newNo: newNo++ };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Every line of both texts in order, the common prefix and suffix trimmed before the LCS pass. */
|
|
131
|
+
function changesBetween(before: ReadonlyArray<string>, after: ReadonlyArray<string>): Change[] {
|
|
132
|
+
let start = 0;
|
|
133
|
+
while (start < before.length && start < after.length && before[start] === after[start]) start += 1;
|
|
134
|
+
let endBefore = before.length;
|
|
135
|
+
let endAfter = after.length;
|
|
136
|
+
while (endBefore > start && endAfter > start && before[endBefore - 1] === after[endAfter - 1]) {
|
|
137
|
+
endBefore -= 1;
|
|
138
|
+
endAfter -= 1;
|
|
139
|
+
}
|
|
140
|
+
const context = (text: string): Change => ({ kind: 'context', text });
|
|
141
|
+
return [
|
|
142
|
+
...before.slice(0, start).map(context),
|
|
143
|
+
...lcs(before.slice(start, endBefore), after.slice(start, endAfter)),
|
|
144
|
+
...before.slice(endBefore).map(context),
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function lcs(a: ReadonlyArray<string>, b: ReadonlyArray<string>): Change[] {
|
|
149
|
+
const removed = (text: string): Change => ({ kind: 'del', text });
|
|
150
|
+
const added = (text: string): Change => ({ kind: 'add', text });
|
|
151
|
+
if (a.length === 0 || b.length === 0 || a.length * b.length > LCS_CELL_BUDGET) return [...a.map(removed), ...b.map(added)];
|
|
152
|
+
// common[i * width + j] = length of the longest common run of a[i:] and b[j:].
|
|
153
|
+
const width = b.length + 1;
|
|
154
|
+
const common = new Uint32Array((a.length + 1) * width);
|
|
155
|
+
const at = (i: number, j: number): number => common[i * width + j] ?? 0;
|
|
156
|
+
for (let i = a.length - 1; i >= 0; i -= 1) {
|
|
157
|
+
for (let j = b.length - 1; j >= 0; j -= 1) {
|
|
158
|
+
common[i * width + j] = a[i] === b[j] ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const changes: Change[] = [];
|
|
162
|
+
let i = 0;
|
|
163
|
+
let j = 0;
|
|
164
|
+
while (i < a.length && j < b.length) {
|
|
165
|
+
const left = a[i] ?? '';
|
|
166
|
+
const right = b[j] ?? '';
|
|
167
|
+
if (left === right) {
|
|
168
|
+
changes.push({ kind: 'context', text: left });
|
|
169
|
+
i += 1;
|
|
170
|
+
j += 1;
|
|
171
|
+
} else if (at(i + 1, j) >= at(i, j + 1)) {
|
|
172
|
+
changes.push(removed(left));
|
|
173
|
+
i += 1;
|
|
174
|
+
} else {
|
|
175
|
+
changes.push(added(right));
|
|
176
|
+
j += 1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...changes, ...a.slice(i).map(removed), ...b.slice(j).map(added)];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The changed lines with CONTEXT lines around them, grouped into hunks and capped at MAX_DIFF_LINES. */
|
|
183
|
+
function hunksAround(lines: ReadonlyArray<DiffLine>): { hunks: DiffHunk[]; truncated: boolean } {
|
|
184
|
+
const ranges: Array<[number, number]> = [];
|
|
185
|
+
lines.forEach((line, index) => {
|
|
186
|
+
if (line.kind === 'context') return;
|
|
187
|
+
const lo = Math.max(0, index - CONTEXT);
|
|
188
|
+
const hi = Math.min(lines.length - 1, index + CONTEXT);
|
|
189
|
+
const last = ranges[ranges.length - 1];
|
|
190
|
+
if (last && lo <= last[1] + 1) last[1] = Math.max(last[1], hi);
|
|
191
|
+
else ranges.push([lo, hi]);
|
|
192
|
+
});
|
|
193
|
+
const hunks: DiffHunk[] = [];
|
|
194
|
+
let shown = 0;
|
|
195
|
+
for (const [lo, hi] of ranges) {
|
|
196
|
+
const kept = lines.slice(lo, Math.min(hi + 1, lo + MAX_DIFF_LINES - shown));
|
|
197
|
+
shown += kept.length;
|
|
198
|
+
if (kept.length > 0) hunks.push(hunkOf(kept));
|
|
199
|
+
if (kept.length < hi + 1 - lo) return { hunks, truncated: true };
|
|
200
|
+
}
|
|
201
|
+
return { hunks, truncated: false };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hunkOf(lines: ReadonlyArray<DiffLine>): DiffHunk {
|
|
205
|
+
const olds = lines.flatMap((line) => (line.oldNo === undefined ? [] : [line.oldNo]));
|
|
206
|
+
const news = lines.flatMap((line) => (line.newNo === undefined ? [] : [line.newNo]));
|
|
207
|
+
return { oldStart: olds[0] ?? 0, oldLines: olds.length, newStart: news[0] ?? 0, newLines: news.length, lines };
|
|
208
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -137,10 +137,46 @@ export type {
|
|
|
137
137
|
export {
|
|
138
138
|
AUTO_APPROVE_PLUGIN_ID,
|
|
139
139
|
AUTO_APPROVE_SUBTYPE,
|
|
140
|
+
AUTO_APPROVE_RESTORED_SUBTYPE,
|
|
141
|
+
autoApproveFromAgent,
|
|
140
142
|
autoApproveFromEvents,
|
|
143
|
+
autoApproveRecorded,
|
|
141
144
|
autoApproveSwitch,
|
|
142
145
|
} from './auto-approve.js';
|
|
143
146
|
|
|
147
|
+
export {
|
|
148
|
+
QUESTION_PLUGIN_ID,
|
|
149
|
+
QUESTION_ASKED,
|
|
150
|
+
QUESTION_RESOLVED,
|
|
151
|
+
QUESTION_CLOSED_AGENT_WENT_ON,
|
|
152
|
+
QUESTION_CLOSED_NEW_MESSAGE,
|
|
153
|
+
questionCloseNote,
|
|
154
|
+
questionOptionSchema,
|
|
155
|
+
questionItemSchema,
|
|
156
|
+
questionAnswerSchema,
|
|
157
|
+
questionAnswersSchema,
|
|
158
|
+
questionRecord,
|
|
159
|
+
openQuestionsFromEvents,
|
|
160
|
+
questionAnswersProblem,
|
|
161
|
+
describeQuestionAnswers,
|
|
162
|
+
questionAnswerDisplay,
|
|
163
|
+
} from './question.js';
|
|
164
|
+
export type {
|
|
165
|
+
QuestionOption,
|
|
166
|
+
QuestionItem,
|
|
167
|
+
QuestionAnswer,
|
|
168
|
+
QuestionAnswers,
|
|
169
|
+
QuestionCloseNote,
|
|
170
|
+
QuestionAskOptions,
|
|
171
|
+
QuestionOutcome,
|
|
172
|
+
QuestionRecord,
|
|
173
|
+
OpenQuestion,
|
|
174
|
+
QuestionAsker,
|
|
175
|
+
SessionQuestions,
|
|
176
|
+
} from './question.js';
|
|
177
|
+
export { emptyDraft, chooseOption, typeOwnAnswer, draftAnswers } from './question-draft.js';
|
|
178
|
+
export type { QuestionDraft } from './question-draft.js';
|
|
179
|
+
|
|
144
180
|
export type {
|
|
145
181
|
ClientSession,
|
|
146
182
|
ProvidersClientView,
|
|
@@ -162,6 +198,7 @@ export type {
|
|
|
162
198
|
PendingToolCall,
|
|
163
199
|
PermissionContext,
|
|
164
200
|
PermissionResolver,
|
|
201
|
+
StandingAllowRule,
|
|
165
202
|
} from './permission.js';
|
|
166
203
|
|
|
167
204
|
export type {
|
|
@@ -221,7 +258,69 @@ export type {
|
|
|
221
258
|
ProviderOAuthResult,
|
|
222
259
|
ProviderOAuthStatus,
|
|
223
260
|
ProviderAuthDescriptor,
|
|
261
|
+
AgentCliState,
|
|
262
|
+
AgentCliOrigin,
|
|
263
|
+
AgentCliReport,
|
|
264
|
+
ProviderAgentCli,
|
|
265
|
+
} from './provider.js';
|
|
266
|
+
export type {
|
|
267
|
+
AgentStepRequest,
|
|
268
|
+
AgentStepHost,
|
|
269
|
+
AgentTurnStart,
|
|
270
|
+
AgentActivity,
|
|
271
|
+
AgentBackgroundWork,
|
|
272
|
+
AgentScheduledWakeup,
|
|
273
|
+
AgentWorkEnd,
|
|
274
|
+
AgentStepEvent,
|
|
275
|
+
AgentGoalStatus,
|
|
276
|
+
AgentOwnModes,
|
|
277
|
+
AgentToolCall,
|
|
278
|
+
AgentToolOutcome,
|
|
279
|
+
AgentApprovalRequest,
|
|
280
|
+
AgentApprovalDecision,
|
|
224
281
|
} from './provider.js';
|
|
282
|
+
export { startingModel } from './starting-model.js';
|
|
283
|
+
export {
|
|
284
|
+
ACCOUNT_LIMIT_NEAR_PERCENT,
|
|
285
|
+
AGENT_ACCOUNT_LIMIT_SUBTYPE,
|
|
286
|
+
accountLimitInForce,
|
|
287
|
+
accountLimitOfEvent,
|
|
288
|
+
recordedAccountLimit,
|
|
289
|
+
sameAccountLimit,
|
|
290
|
+
withKnownReset,
|
|
291
|
+
type AgentAccountLimit,
|
|
292
|
+
type RecordedAccountLimit,
|
|
293
|
+
} from './agent-account-limit.js';
|
|
294
|
+
export {
|
|
295
|
+
AGENT_CONTEXT_WINDOW_SUBTYPE,
|
|
296
|
+
contextWindowOfEvent,
|
|
297
|
+
isContextWindowSize,
|
|
298
|
+
recordedContextWindows,
|
|
299
|
+
withRecordedContextWindows,
|
|
300
|
+
type RecordedContextWindow,
|
|
301
|
+
} from './agent-context-window.js';
|
|
302
|
+
export { AGENT_NO_REPLY_SUBTYPE, AGENT_NO_REPLY_TEXT, isAgentNoReplyEvent } from './agent-no-reply.js';
|
|
303
|
+
export {
|
|
304
|
+
AGENT_BACKGROUND_WORK_ENDED_SUBTYPE,
|
|
305
|
+
AGENT_BACKGROUND_WORK_SUBTYPE,
|
|
306
|
+
AGENT_WORK_OUTPUT_LIMIT,
|
|
307
|
+
SUBAGENT_MESSAGE_SUBTYPE,
|
|
308
|
+
agentActivityRecord,
|
|
309
|
+
isAgentWorkEvent,
|
|
310
|
+
isSubagentEvent,
|
|
311
|
+
type AgentActivityRecord,
|
|
312
|
+
type SubagentNames,
|
|
313
|
+
} from './agent-work.js';
|
|
314
|
+
export { AGENT_CONNECTION_PLUGIN_ID, AGENT_GOAL_SUBTYPE, AGENT_SESSION_SUBTYPE, SUBAGENTS_PLUGIN_ID, agentSessionFromEvents } from './agent-step.js';
|
|
315
|
+
export {
|
|
316
|
+
conversationTurns,
|
|
317
|
+
EARLIER_CONVERSATION_CHARS,
|
|
318
|
+
renderEarlierConversation,
|
|
319
|
+
renderMissedTurns,
|
|
320
|
+
searchConversation,
|
|
321
|
+
type ConversationMatch,
|
|
322
|
+
type ConversationTurn,
|
|
323
|
+
} from './conversation-history.js';
|
|
225
324
|
export {
|
|
226
325
|
encodeLoginAuthUrl,
|
|
227
326
|
decodeLoginAuthUrl,
|
|
@@ -236,18 +335,36 @@ export type {
|
|
|
236
335
|
DiffHunk,
|
|
237
336
|
DiffRow,
|
|
238
337
|
FileDiffDisplay,
|
|
338
|
+
AnsweredQuestion,
|
|
339
|
+
QuestionAnswerDisplay,
|
|
340
|
+
ListedStep,
|
|
341
|
+
StepListDisplay,
|
|
342
|
+
StepStatus,
|
|
239
343
|
ToolDisplay,
|
|
240
344
|
ToolDisplayResult,
|
|
345
|
+
ToolImage,
|
|
241
346
|
} from './tool-display.js';
|
|
242
347
|
export {
|
|
243
348
|
isToolDisplayResult,
|
|
244
349
|
isToolDisplay,
|
|
245
350
|
isFileDiffDisplay,
|
|
351
|
+
isQuestionAnswerDisplay,
|
|
352
|
+
isStepListDisplay,
|
|
353
|
+
stepStatusOf,
|
|
354
|
+
stepsFromTodos,
|
|
355
|
+
stepListOutput,
|
|
356
|
+
stepListProgress,
|
|
357
|
+
questionAnswerLines,
|
|
246
358
|
fileDiffSummary,
|
|
247
359
|
fileDiffVerb,
|
|
248
360
|
diffGutterNo,
|
|
249
361
|
toDiffRows,
|
|
362
|
+
toolImagesOf,
|
|
363
|
+
imageToolOutput,
|
|
250
364
|
} from './tool-display.js';
|
|
365
|
+
export { fileDiffFromText, fileDiffFromPatch, parseUnifiedDiff, diffPathIn, type PatchHunk } from './file-diff.js';
|
|
366
|
+
export { agentComputerUseOf, type AgentComputerUse } from './agent-computer-use.js';
|
|
367
|
+
export { toolOutputForModel, type ToolOutputForModel, type ToolImageBlock } from './tool-output.js';
|
|
251
368
|
export type {
|
|
252
369
|
ViewNode,
|
|
253
370
|
ViewAction,
|
|
@@ -341,7 +458,10 @@ export {
|
|
|
341
458
|
createCallbackResolver,
|
|
342
459
|
createAllowListResolver,
|
|
343
460
|
createDeferredPermissionResolver,
|
|
461
|
+
createSessionGrants,
|
|
462
|
+
alwaysAllowRule,
|
|
344
463
|
evaluateToolRule,
|
|
464
|
+
type SessionGrants,
|
|
345
465
|
type CallbackResolverOptions,
|
|
346
466
|
type PermissionPromptHandler,
|
|
347
467
|
type DeferredPermissionResolver,
|
|
@@ -663,5 +783,5 @@ export {
|
|
|
663
783
|
type InstallTarget,
|
|
664
784
|
} from './install-hints.js';
|
|
665
785
|
|
|
666
|
-
export { z } from 'zod';
|
|
786
|
+
export { z } from 'zod/v3';
|
|
667
787
|
export { workflowApprovalItemSchema, workflowApprovalItemsSchema } from './workflow-approval.js';
|
|
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
5
|
import { posixFileModes } from '@moxxy/vitest-preset/platform';
|
|
6
|
-
import { z } from 'zod';
|
|
6
|
+
import { z } from 'zod/v3';
|
|
7
7
|
import { createJsonFileStore } from './json-file-store.js';
|
|
8
8
|
import { removeDir } from '@moxxy/vitest-preset/fs';
|
|
9
9
|
|
package/src/loop-helpers.test.ts
CHANGED
|
@@ -192,6 +192,21 @@ describe('projectMessagesFromLog', () => {
|
|
|
192
192
|
}
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
+
it('projects only the text of an answered Question, not the copy kept for the transcript', () => {
|
|
196
|
+
const output = {
|
|
197
|
+
forModel: 'The user answered:\n- "Which colour?" → Blue',
|
|
198
|
+
display: { kind: 'question-answer' as const, questions: [{ question: 'Which colour?', chosen: ['Blue'] }] },
|
|
199
|
+
};
|
|
200
|
+
const log = reader([
|
|
201
|
+
event(0, { type: 'user_prompt', turnId: t1, source: 'user', text: 'make a button' }),
|
|
202
|
+
event(1, { type: 'tool_call_requested', turnId: t1, source: 'model', callId: 'c1', name: 'ask_user', input: {} }),
|
|
203
|
+
event(2, { type: 'tool_result', turnId: t1, source: 'tool', callId: 'c1', ok: true, output }),
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
const toolResult = projectMessagesFromLog({ log }).find((m) => m.role === 'tool_result');
|
|
207
|
+
expect(toolResult?.content[0]).toMatchObject({ type: 'tool_result', content: output.forModel });
|
|
208
|
+
});
|
|
209
|
+
|
|
195
210
|
it('projects an image-shaped tool result as an image block, not stringified text', () => {
|
|
196
211
|
// A screenshot/clipboard tool returns { mediaType, base64 }. The model must
|
|
197
212
|
// SEE the pixels (image block), not a giant base64 string JSON-stringified
|