tinker-agent 1.0.65 → 1.2.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/CHANGELOG.md +36 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +90 -9
- package/bin/tinker.js +39 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/has-flag/index.d.ts +39 -0
- package/node_modules/has-flag/index.js +8 -0
- package/node_modules/has-flag/license +9 -0
- package/node_modules/has-flag/package.json +49 -0
- package/node_modules/has-flag/readme.md +74 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +12 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +53 -0
- package/node_modules/is-fullwidth-code-point/readme.md +31 -0
- package/node_modules/markdansi/LICENSE +21 -0
- package/node_modules/markdansi/README.md +167 -0
- package/node_modules/markdansi/dist/ast.d.ts +99 -0
- package/node_modules/markdansi/dist/ast.js +1 -0
- package/node_modules/markdansi/dist/cli.d.ts +17 -0
- package/node_modules/markdansi/dist/cli.js +208 -0
- package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
- package/node_modules/markdansi/dist/hyperlink.js +26 -0
- package/node_modules/markdansi/dist/index.d.ts +10 -0
- package/node_modules/markdansi/dist/index.js +16 -0
- package/node_modules/markdansi/dist/parser.d.ts +2 -0
- package/node_modules/markdansi/dist/parser.js +205 -0
- package/node_modules/markdansi/dist/render.d.ts +9 -0
- package/node_modules/markdansi/dist/render.js +680 -0
- package/node_modules/markdansi/dist/stream.d.ts +39 -0
- package/node_modules/markdansi/dist/stream.js +192 -0
- package/node_modules/markdansi/dist/theme.d.ts +18 -0
- package/node_modules/markdansi/dist/theme.js +105 -0
- package/node_modules/markdansi/dist/types.d.ts +58 -0
- package/node_modules/markdansi/dist/types.js +1 -0
- package/node_modules/markdansi/dist/wrap.d.ts +10 -0
- package/node_modules/markdansi/dist/wrap.js +73 -0
- package/node_modules/markdansi/docs/spec.md +110 -0
- package/node_modules/markdansi/package.json +78 -0
- package/node_modules/markdansi/tsconfig.json +18 -0
- package/node_modules/marked/LICENSE +44 -0
- package/node_modules/marked/README.md +115 -0
- package/node_modules/marked/bin/main.js +284 -0
- package/node_modules/marked/bin/marked.js +16 -0
- package/node_modules/marked/lib/marked.d.ts +760 -0
- package/node_modules/marked/lib/marked.esm.js +77 -0
- package/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/node_modules/marked/lib/marked.umd.js +79 -0
- package/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/node_modules/marked/man/marked.1 +113 -0
- package/node_modules/marked/man/marked.1.md +93 -0
- package/node_modules/marked/package.json +103 -0
- package/node_modules/slice-ansi/index.d.ts +19 -0
- package/node_modules/slice-ansi/index.js +317 -0
- package/node_modules/slice-ansi/license +10 -0
- package/node_modules/slice-ansi/package.json +59 -0
- package/node_modules/slice-ansi/readme.md +55 -0
- package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +203 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +65 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/supports-color/browser.d.ts +1 -0
- package/node_modules/supports-color/browser.js +35 -0
- package/node_modules/supports-color/index.d.ts +55 -0
- package/node_modules/supports-color/index.js +202 -0
- package/node_modules/supports-color/license +9 -0
- package/node_modules/supports-color/package.json +64 -0
- package/node_modules/supports-color/readme.md +75 -0
- package/node_modules/supports-hyperlinks/browser.js +10 -0
- package/node_modules/supports-hyperlinks/index.d.ts +25 -0
- package/node_modules/supports-hyperlinks/index.js +163 -0
- package/node_modules/supports-hyperlinks/license +10 -0
- package/node_modules/supports-hyperlinks/package.json +47 -0
- package/node_modules/supports-hyperlinks/readme.md +45 -0
- package/package.json +41 -10
- package/patches/markdansi@0.3.2.patch +22 -2
- package/src/agent/context-builder.ts +24 -10
- package/src/agent/context-meter.ts +89 -5
- package/src/agent/loop.ts +107 -10
- package/src/agent/runtime-session.ts +1437 -82
- package/src/agent/session-ledger.ts +169 -29
- package/src/agent/types.ts +12 -1
- package/src/agent/user-prompt-projection.ts +62 -0
- package/src/cli/config.ts +57 -14
- package/src/cli/model-profiles.ts +124 -0
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/tui-runner.tsx +68 -30
- package/src/context/compiled-context-hash.ts +1 -1
- package/src/context/compiled-context-validator.ts +92 -15
- package/src/context/context-automation-policy.ts +121 -0
- package/src/context/context-manager.ts +390 -11
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +11 -1
- package/src/context/context-revision-compiler.ts +190 -27
- package/src/context/context-revision.ts +105 -19
- package/src/context/context-surface.ts +329 -0
- package/src/context/prefix-retirement-planner.ts +570 -0
- package/src/context/protocol-frame.ts +30 -3
- package/src/context/recall-retirement-contract.ts +27 -0
- package/src/context/swap-planner.ts +34 -10
- package/src/events/observation-text-log.ts +3 -3
- package/src/events/stdout-event-printer.ts +39 -3
- package/src/events/types.ts +172 -35
- package/src/ids/runtime-id.ts +6 -0
- package/src/ids/uuid-v7.ts +7 -0
- package/src/image/image-asset-store.ts +319 -0
- package/src/image/image-input-policy.ts +14 -0
- package/src/image/image-probe.ts +233 -0
- package/src/image/image-types.ts +262 -0
- package/src/instructions/project-instructions.ts +4 -1
- package/src/mcp/mcp-manager.ts +83 -19
- package/src/model/fake-model-client.ts +9 -1
- package/src/model/input-token-estimator.ts +25 -0
- package/src/model/model-client.ts +60 -0
- package/src/model/model-request-preflight.ts +1 -0
- package/src/model/moonshot-input-token-estimator.ts +107 -0
- package/src/model/openai-chat-mapping.ts +77 -1
- package/src/model/openai-chat-model-client.ts +389 -59
- package/src/model/openai-chat-stream.ts +272 -0
- package/src/model/prompt-prefix-hash.ts +2 -4
- package/src/model/token-estimator.ts +4 -0
- package/src/observation/observation-builder.ts +29 -0
- package/src/session/resume-projection.ts +94 -34
- package/src/session/session-catalog.ts +4 -4
- package/src/session/session-errors.ts +1 -1
- package/src/session/session-history-reader.ts +151 -4
- package/src/session/session-last-response-reader.ts +189 -0
- package/src/session/session-schema.ts +349 -35
- package/src/session/session-store.ts +3266 -255
- package/src/session/sqlite-session-ledger.ts +9 -4
- package/src/skills/skill-catalog.ts +182 -0
- package/src/skills/skill-context.ts +364 -0
- package/src/skills/skill-loader.ts +779 -0
- package/src/skills/skill-tool.ts +297 -0
- package/src/tools/edit.ts +51 -64
- package/src/tools/ensure-parent-directory.ts +6 -0
- package/src/tools/read.ts +2 -2
- package/src/tools/recall.ts +43 -40
- package/src/tools/registry.ts +24 -3
- package/src/tools/ripgrep.ts +3 -2
- package/src/tools/types.ts +43 -4
- package/src/tools/write.ts +20 -31
- package/src/tui/app.tsx +333 -96
- package/src/tui/clipboard.ts +5 -0
- package/src/tui/components/mcp-panel.tsx +28 -0
- package/src/tui/components/prompt-input.tsx +790 -257
- package/src/tui/components/skills-panel.tsx +31 -0
- package/src/tui/components/timeline.tsx +30 -1
- package/src/tui/event-store.ts +85 -2
- package/src/tui/file-mention.ts +234 -0
- package/src/tui/line-editor.ts +1 -0
- package/src/tui/project-slash-commands.ts +229 -0
- package/src/tui/prompt-draft.ts +510 -0
- package/src/tui/prompt-history.ts +294 -22
- package/src/tui/slash-commands.ts +46 -2
- package/src/tui/tui-session-controller.ts +110 -48
- package/src/tui/workspace-file-search.ts +94 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Box, Text } from "ink";
|
|
2
|
+
import type { RuntimeSkillsSnapshot } from "../../agent/runtime-session";
|
|
3
|
+
|
|
4
|
+
export function SkillsPanel(props: { snapshot: RuntimeSkillsSnapshot }) {
|
|
5
|
+
return (
|
|
6
|
+
<Box flexDirection="column">
|
|
7
|
+
<Text bold>Agent Skills</Text>
|
|
8
|
+
{props.snapshot.skills.length === 0 ? (
|
|
9
|
+
<Text color="yellow"> no skills available</Text>
|
|
10
|
+
) : (
|
|
11
|
+
props.snapshot.skills.map((skill) => (
|
|
12
|
+
<Box key={skill.name} flexDirection="column">
|
|
13
|
+
<Text>
|
|
14
|
+
{" "}
|
|
15
|
+
<Text bold>{skill.name}</Text>
|
|
16
|
+
{` (${skill.scope}${skill.active ? ", active" : ""})`}
|
|
17
|
+
</Text>
|
|
18
|
+
<Text dimColor> {skill.description}</Text>
|
|
19
|
+
</Box>
|
|
20
|
+
))
|
|
21
|
+
)}
|
|
22
|
+
{props.snapshot.shadowedNames.length === 0 ? null : (
|
|
23
|
+
<>
|
|
24
|
+
<Text> </Text>
|
|
25
|
+
<Text bold>Shadowed user skills</Text>
|
|
26
|
+
<Text> {props.snapshot.shadowedNames.join(", ")}</Text>
|
|
27
|
+
</>
|
|
28
|
+
)}
|
|
29
|
+
</Box>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -36,7 +36,11 @@ function renderTimelineItem(item: TimelineItem) {
|
|
|
36
36
|
return (
|
|
37
37
|
<Fragment key={item.id}>
|
|
38
38
|
<Text color="gray">- {item.label}</Text>
|
|
39
|
-
|
|
39
|
+
{item.userPrompt === undefined ? (
|
|
40
|
+
<Text color={colorForStatus(item.status)}>{formatTimelineItem(item)}</Text>
|
|
41
|
+
) : (
|
|
42
|
+
renderUserPrompt(item.userPrompt)
|
|
43
|
+
)}
|
|
40
44
|
{renderItemBash(item)}
|
|
41
45
|
{renderItemDiff(item)}
|
|
42
46
|
</Fragment>
|
|
@@ -52,6 +56,31 @@ function renderTimelineItem(item: TimelineItem) {
|
|
|
52
56
|
);
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
function renderUserPrompt(prompt: NonNullable<TimelineItem["userPrompt"]>) {
|
|
60
|
+
const chars = [...prompt.text];
|
|
61
|
+
const fragments: React.ReactNode[] = [];
|
|
62
|
+
let offset = 0;
|
|
63
|
+
for (const image of prompt.images) {
|
|
64
|
+
fragments.push(chars.slice(offset, image.range.start).join(""));
|
|
65
|
+
fragments.push(
|
|
66
|
+
<Text key={`${image.range.start}:${image.label}`} color="cyan" bold>
|
|
67
|
+
{chars.slice(image.range.start, image.range.end).join("")}
|
|
68
|
+
<Text dimColor> ({image.originalName})</Text>
|
|
69
|
+
</Text>,
|
|
70
|
+
);
|
|
71
|
+
offset = image.range.end;
|
|
72
|
+
}
|
|
73
|
+
fragments.push(chars.slice(offset).join(""));
|
|
74
|
+
if (prompt.omittedImageCount > 0) {
|
|
75
|
+
fragments.push(
|
|
76
|
+
<Text key="omitted-images" dimColor>
|
|
77
|
+
{` [${prompt.omittedImageCount} image(s) omitted]`}
|
|
78
|
+
</Text>,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return <Text color="white">{fragments}</Text>;
|
|
82
|
+
}
|
|
83
|
+
|
|
55
84
|
function renderItemDiff(item: TimelineItem) {
|
|
56
85
|
if (item.diff === undefined || item.diff.length === 0) {
|
|
57
86
|
return null;
|
package/src/tui/event-store.ts
CHANGED
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
type TuiProjectionPolicy,
|
|
18
18
|
validateTuiProjectionPolicy,
|
|
19
19
|
} from "./tui-projection-policy";
|
|
20
|
+
import {
|
|
21
|
+
MAX_TIMELINE_PROMPT_CODE_POINTS,
|
|
22
|
+
truncateUserPromptProjection,
|
|
23
|
+
type UserPromptProjection,
|
|
24
|
+
} from "../agent/user-prompt-projection";
|
|
20
25
|
|
|
21
26
|
export type TimelineItem = {
|
|
22
27
|
id: string;
|
|
@@ -27,6 +32,7 @@ export type TimelineItem = {
|
|
|
27
32
|
diff?: DiffHunk[];
|
|
28
33
|
diffTruncated?: boolean;
|
|
29
34
|
bash?: BashDisplayDetail;
|
|
35
|
+
userPrompt?: UserPromptProjection;
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
export type TuiTurnProjection = {
|
|
@@ -96,6 +102,11 @@ export function reduceTuiProjection(
|
|
|
96
102
|
contextBudget: event.data.contextBudget,
|
|
97
103
|
};
|
|
98
104
|
case "session.resumed":
|
|
105
|
+
return {
|
|
106
|
+
...state,
|
|
107
|
+
contextProfile: event.data.contextProfile,
|
|
108
|
+
contextBudget: event.data.contextBudget,
|
|
109
|
+
};
|
|
99
110
|
case "session.interrupted_frame_recovered":
|
|
100
111
|
return state;
|
|
101
112
|
case "context.usage.updated":
|
|
@@ -112,6 +123,10 @@ export function reduceTuiProjection(
|
|
|
112
123
|
"turn.started turnNumber",
|
|
113
124
|
);
|
|
114
125
|
parseEventTimestamp(event.timestamp);
|
|
126
|
+
const userPrompt = truncateUserPromptProjection(
|
|
127
|
+
event.data.userPrompt,
|
|
128
|
+
MAX_TIMELINE_PROMPT_CODE_POINTS,
|
|
129
|
+
);
|
|
115
130
|
return {
|
|
116
131
|
...state,
|
|
117
132
|
status: "running",
|
|
@@ -127,7 +142,8 @@ export function reduceTuiProjection(
|
|
|
127
142
|
{
|
|
128
143
|
id: `turn-${turnId}-prompt-${event.eventSequence}`,
|
|
129
144
|
label: "prompt",
|
|
130
|
-
text:
|
|
145
|
+
text: userPrompt.text,
|
|
146
|
+
userPrompt,
|
|
131
147
|
status: "text",
|
|
132
148
|
},
|
|
133
149
|
],
|
|
@@ -223,6 +239,47 @@ export function reduceTuiProjection(
|
|
|
223
239
|
},
|
|
224
240
|
policy.sessionNoticeLimit,
|
|
225
241
|
);
|
|
242
|
+
case "skills.catalog.loaded":
|
|
243
|
+
return event.data.shadowedNames.length === 0
|
|
244
|
+
? state
|
|
245
|
+
: appendNotice(
|
|
246
|
+
state,
|
|
247
|
+
{
|
|
248
|
+
id: `notice-skills-catalog-${event.eventSequence}`,
|
|
249
|
+
label: "skills",
|
|
250
|
+
text: `project skills shadowed user skills -> ${event.data.shadowedNames.join(", ")}`,
|
|
251
|
+
status: "info",
|
|
252
|
+
},
|
|
253
|
+
policy.sessionNoticeLimit,
|
|
254
|
+
);
|
|
255
|
+
case "skills.updated": {
|
|
256
|
+
const changes = [
|
|
257
|
+
event.data.activated.length === 0
|
|
258
|
+
? undefined
|
|
259
|
+
: `activated ${event.data.activated.join(", ")}`,
|
|
260
|
+
event.data.refreshed.length === 0
|
|
261
|
+
? undefined
|
|
262
|
+
: `refreshed ${event.data.refreshed.join(", ")}`,
|
|
263
|
+
event.data.deactivated.length === 0
|
|
264
|
+
? undefined
|
|
265
|
+
: `deactivated ${event.data.deactivated.join(", ")}`,
|
|
266
|
+
event.data.unavailable.length === 0
|
|
267
|
+
? undefined
|
|
268
|
+
: `unavailable ${event.data.unavailable.join(", ")}`,
|
|
269
|
+
].filter((entry): entry is string => entry !== undefined);
|
|
270
|
+
return changes.length === 0
|
|
271
|
+
? state
|
|
272
|
+
: appendNotice(
|
|
273
|
+
state,
|
|
274
|
+
{
|
|
275
|
+
id: `notice-skills-updated-${event.eventSequence}`,
|
|
276
|
+
label: "skills",
|
|
277
|
+
text: `skills updated -> ${changes.join("; ")}`,
|
|
278
|
+
status: "info",
|
|
279
|
+
},
|
|
280
|
+
policy.sessionNoticeLimit,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
226
283
|
case "diagnostic.sink_failed":
|
|
227
284
|
return appendNotice(
|
|
228
285
|
state,
|
|
@@ -301,15 +358,31 @@ export function reduceTuiProjection(
|
|
|
301
358
|
case "context.shadow.planned":
|
|
302
359
|
case "context.shadow.failed":
|
|
303
360
|
case "context.revision.started":
|
|
304
|
-
case "context.revision.finished":
|
|
305
361
|
case "context.revision.failed":
|
|
306
362
|
case "tool.observation":
|
|
307
363
|
return state;
|
|
364
|
+
case "context.revision.finished":
|
|
365
|
+
return event.data.strategy === "surface_refresh"
|
|
366
|
+
? appendNotice(
|
|
367
|
+
state,
|
|
368
|
+
{
|
|
369
|
+
id: `notice-context-refresh-${event.eventSequence}`,
|
|
370
|
+
label: "context",
|
|
371
|
+
text: `runtime context refreshed on resume -> ${event.data.changed.map(formatSurfaceComponent).join(", ")}`,
|
|
372
|
+
status: "info",
|
|
373
|
+
},
|
|
374
|
+
policy.sessionNoticeLimit,
|
|
375
|
+
)
|
|
376
|
+
: state;
|
|
308
377
|
default:
|
|
309
378
|
return assertNever(event);
|
|
310
379
|
}
|
|
311
380
|
}
|
|
312
381
|
|
|
382
|
+
function formatSurfaceComponent(component: string): string {
|
|
383
|
+
return component.replaceAll("_", " ");
|
|
384
|
+
}
|
|
385
|
+
|
|
313
386
|
export const applyAgentEvent = reduceTuiProjection;
|
|
314
387
|
|
|
315
388
|
export function visibleTimelineItems(state: TuiProjectionState): TimelineItem[] {
|
|
@@ -619,6 +692,9 @@ function toolCallSummary(input: { name: string; args: unknown }): string {
|
|
|
619
692
|
if (input.name === "TaskList") {
|
|
620
693
|
return "TaskList";
|
|
621
694
|
}
|
|
695
|
+
if (input.name === "Skill") {
|
|
696
|
+
return `Skill ${stringProperty(asRecord(input.args), "name") ?? ""}`.trim();
|
|
697
|
+
}
|
|
622
698
|
|
|
623
699
|
const filePath = toolPath(input.args);
|
|
624
700
|
return `${input.name}${filePath === undefined ? "" : ` ${filePath}`}`;
|
|
@@ -686,6 +762,11 @@ function toolRawResultSummary(name: string, args: unknown, raw: ToolRawResult):
|
|
|
686
762
|
return raw.mode === "search"
|
|
687
763
|
? `${base} -> ${raw.page.hits.length} historical match${raw.page.hits.length === 1 ? "" : "es"}`
|
|
688
764
|
: `${base} -> ${raw.page.returnedBytes} historical bytes`;
|
|
765
|
+
case "skill":
|
|
766
|
+
if (!raw.ok) {
|
|
767
|
+
return `${base} failed -> ${boundedToolError(raw.error)}`;
|
|
768
|
+
}
|
|
769
|
+
return `skill ${raw.name} ${raw.status.replaceAll("_", " ")}`;
|
|
689
770
|
case "task_list":
|
|
690
771
|
return `${base} -> ${raw.tasks.length} task${raw.tasks.length === 1 ? "" : "s"}, ${raw.runningCount} running`;
|
|
691
772
|
case "task_output":
|
|
@@ -748,6 +829,7 @@ function toolRawResultBashDetail(raw: ToolRawResult): Pick<TimelineItem, "bash">
|
|
|
748
829
|
case "web_search":
|
|
749
830
|
case "web_fetch":
|
|
750
831
|
case "recall":
|
|
832
|
+
case "skill":
|
|
751
833
|
case "mcp":
|
|
752
834
|
case "generic":
|
|
753
835
|
return {};
|
|
@@ -781,6 +863,7 @@ function toolRawResultDiff(
|
|
|
781
863
|
case "web_search":
|
|
782
864
|
case "web_fetch":
|
|
783
865
|
case "recall":
|
|
866
|
+
case "skill":
|
|
784
867
|
case "mcp":
|
|
785
868
|
case "generic":
|
|
786
869
|
return {};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { LineEditorState } from "./line-editor";
|
|
2
|
+
|
|
3
|
+
export const MAX_FILE_MENTION_RESULTS = 8;
|
|
4
|
+
|
|
5
|
+
export type FileMention = {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
query: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FileMentionMatch = {
|
|
12
|
+
path: string;
|
|
13
|
+
indices: readonly number[];
|
|
14
|
+
score: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function findFileMention(editor: LineEditorState): FileMention | undefined {
|
|
18
|
+
const chars = [...editor.value];
|
|
19
|
+
let anchor: number;
|
|
20
|
+
|
|
21
|
+
if (editor.cursor < chars.length && !isWhitespace(chars[editor.cursor])) {
|
|
22
|
+
anchor = editor.cursor;
|
|
23
|
+
} else if (editor.cursor > 0 && !isWhitespace(chars[editor.cursor - 1])) {
|
|
24
|
+
anchor = editor.cursor - 1;
|
|
25
|
+
} else {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let start = anchor;
|
|
30
|
+
while (start > 0 && !isWhitespace(chars[start - 1])) {
|
|
31
|
+
start -= 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (chars[start] !== "@") {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let end = anchor + 1;
|
|
39
|
+
while (end < chars.length && !isWhitespace(chars[end])) {
|
|
40
|
+
end += 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
start,
|
|
45
|
+
end,
|
|
46
|
+
query: chars.slice(start + 1, end).join(""),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function replaceFileMention(
|
|
51
|
+
editor: LineEditorState,
|
|
52
|
+
mention: FileMention,
|
|
53
|
+
filePath: string,
|
|
54
|
+
): LineEditorState {
|
|
55
|
+
const chars = [...editor.value];
|
|
56
|
+
const replacement = [...filePath];
|
|
57
|
+
const suffix = chars.slice(mention.end);
|
|
58
|
+
const reusesSeparator = isHorizontalWhitespace(suffix[0]);
|
|
59
|
+
const separator = reusesSeparator ? [] : [" "];
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
value: [
|
|
63
|
+
...chars.slice(0, mention.start),
|
|
64
|
+
...replacement,
|
|
65
|
+
...separator,
|
|
66
|
+
...suffix,
|
|
67
|
+
].join(""),
|
|
68
|
+
cursor: mention.start + replacement.length + 1,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function rankWorkspaceFiles(
|
|
73
|
+
files: readonly string[],
|
|
74
|
+
query: string,
|
|
75
|
+
limit = MAX_FILE_MENTION_RESULTS,
|
|
76
|
+
): FileMentionMatch[] {
|
|
77
|
+
if (query === "") {
|
|
78
|
+
return files
|
|
79
|
+
.map((filePath) => ({ path: filePath, indices: [], score: 0 }))
|
|
80
|
+
.sort((left, right) => compareShallowPaths(left.path, right.path))
|
|
81
|
+
.slice(0, limit);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return files
|
|
85
|
+
.map((filePath) => fuzzyMatchPath(filePath, query))
|
|
86
|
+
.filter((match): match is FileMentionMatch => match !== undefined)
|
|
87
|
+
.sort(compareFileMatches)
|
|
88
|
+
.slice(0, limit);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function fuzzyMatchPath(filePath: string, query: string): FileMentionMatch | undefined {
|
|
92
|
+
const pathChars = [...filePath];
|
|
93
|
+
const queryChars = [...query];
|
|
94
|
+
const fullPathMatch = fuzzySubsequence(pathChars, queryChars, 0);
|
|
95
|
+
const basenameStart = findBasenameStart(pathChars);
|
|
96
|
+
const basenameMatch = fuzzySubsequence(
|
|
97
|
+
pathChars.slice(basenameStart),
|
|
98
|
+
queryChars,
|
|
99
|
+
basenameStart,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (basenameMatch !== undefined) {
|
|
103
|
+
return {
|
|
104
|
+
path: filePath,
|
|
105
|
+
indices: basenameMatch.indices,
|
|
106
|
+
score: basenameMatch.score + 200,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (fullPathMatch === undefined) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
path: filePath,
|
|
116
|
+
indices: fullPathMatch.indices,
|
|
117
|
+
score: fullPathMatch.score,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function fuzzySubsequence(
|
|
122
|
+
candidate: readonly string[],
|
|
123
|
+
query: readonly string[],
|
|
124
|
+
indexOffset: number,
|
|
125
|
+
): { indices: number[]; score: number } | undefined {
|
|
126
|
+
const indices: number[] = [];
|
|
127
|
+
let candidateIndex = 0;
|
|
128
|
+
let previousIndex = -1;
|
|
129
|
+
let score = 0;
|
|
130
|
+
|
|
131
|
+
for (const queryChar of query) {
|
|
132
|
+
const normalizedQueryChar = queryChar.toLowerCase();
|
|
133
|
+
let matchIndex = -1;
|
|
134
|
+
|
|
135
|
+
while (candidateIndex < candidate.length) {
|
|
136
|
+
if (candidate[candidateIndex]?.toLowerCase() === normalizedQueryChar) {
|
|
137
|
+
matchIndex = candidateIndex;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
candidateIndex += 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (matchIndex === -1) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
score += 100;
|
|
148
|
+
if (previousIndex >= 0) {
|
|
149
|
+
const gap = matchIndex - previousIndex - 1;
|
|
150
|
+
score += gap === 0 ? 40 : -gap;
|
|
151
|
+
}
|
|
152
|
+
if (isBoundary(candidate, matchIndex)) {
|
|
153
|
+
score += 30;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
indices.push(matchIndex + indexOffset);
|
|
157
|
+
previousIndex = matchIndex;
|
|
158
|
+
candidateIndex = matchIndex + 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
score -= candidate.length - query.length;
|
|
162
|
+
return { indices, score };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function compareFileMatches(left: FileMentionMatch, right: FileMentionMatch): number {
|
|
166
|
+
if (left.score !== right.score) {
|
|
167
|
+
return right.score - left.score;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const depthDifference = pathDepth(left.path) - pathDepth(right.path);
|
|
171
|
+
if (depthDifference !== 0) {
|
|
172
|
+
return depthDifference;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lengthDifference = [...left.path].length - [...right.path].length;
|
|
176
|
+
return lengthDifference === 0
|
|
177
|
+
? comparePathText(left.path, right.path)
|
|
178
|
+
: lengthDifference;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function compareShallowPaths(left: string, right: string): number {
|
|
182
|
+
const depthDifference = pathDepth(left) - pathDepth(right);
|
|
183
|
+
return depthDifference === 0 ? comparePathText(left, right) : depthDifference;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function comparePathText(left: string, right: string): number {
|
|
187
|
+
const normalizedLeft = left.toLowerCase();
|
|
188
|
+
const normalizedRight = right.toLowerCase();
|
|
189
|
+
|
|
190
|
+
if (normalizedLeft < normalizedRight) {
|
|
191
|
+
return -1;
|
|
192
|
+
}
|
|
193
|
+
if (normalizedLeft > normalizedRight) {
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function pathDepth(filePath: string): number {
|
|
200
|
+
return [...filePath].filter((char) => char === "/" || char === "\\").length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function findBasenameStart(chars: readonly string[]): number {
|
|
204
|
+
for (let index = chars.length - 1; index >= 0; index -= 1) {
|
|
205
|
+
if (chars[index] === "/" || chars[index] === "\\") {
|
|
206
|
+
return index + 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isBoundary(chars: readonly string[], index: number): boolean {
|
|
213
|
+
if (index === 0) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const previous = chars[index - 1];
|
|
218
|
+
return (
|
|
219
|
+
previous === "/" ||
|
|
220
|
+
previous === "\\" ||
|
|
221
|
+
previous === "." ||
|
|
222
|
+
previous === "_" ||
|
|
223
|
+
previous === "-" ||
|
|
224
|
+
isWhitespace(previous)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isHorizontalWhitespace(char: string | undefined): boolean {
|
|
229
|
+
return char !== undefined && char !== "\n" && char !== "\r" && isWhitespace(char);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isWhitespace(char: string | undefined): boolean {
|
|
233
|
+
return char !== undefined && /\s/u.test(char);
|
|
234
|
+
}
|