@oh-my-pi/pi-coding-agent 16.3.2 → 16.3.4
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 +44 -0
- package/dist/cli.js +3553 -3540
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/cli/update-cli.test.d.ts +1 -0
- package/dist/types/commands/update.d.ts +5 -0
- package/dist/types/config/config-file.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/dap/client.d.ts +9 -1
- package/dist/types/edit/file-snapshot-store.d.ts +9 -1
- package/dist/types/edit/hashline/execute.d.ts +1 -1
- package/dist/types/edit/hashline/params.d.ts +3 -6
- package/dist/types/edit/renderer.d.ts +1 -0
- package/dist/types/extensibility/plugins/parser.d.ts +2 -1
- package/dist/types/hindsight/client.d.ts +15 -11
- package/dist/types/hindsight/client.test.d.ts +1 -0
- package/dist/types/mcp/smithery-registry.d.ts +1 -0
- package/dist/types/mcp/smithery-registry.test.d.ts +1 -0
- package/dist/types/modes/components/advisor-message.d.ts +4 -2
- package/dist/types/modes/components/tool-execution.d.ts +9 -6
- package/dist/types/modes/components/transcript-container.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/session/indexed-session-storage.d.ts +2 -2
- package/dist/types/session/session-storage.d.ts +31 -3
- package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +1 -0
- package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +1 -0
- package/dist/types/ssh/connection-manager.d.ts +19 -0
- package/dist/types/ssh/sshfs-mount.d.ts +10 -1
- package/dist/types/subprocess/worker-client.d.ts +20 -6
- package/dist/types/tools/bash.d.ts +27 -0
- package/dist/types/tools/renderers.d.ts +13 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/utils/fetch-timeout.d.ts +4 -0
- package/dist/types/web/search/providers/base.d.ts +1 -0
- package/dist/types/web/search/providers/gemini.d.ts +1 -0
- package/package.json +12 -12
- package/scripts/generate-legacy-pi-bundled-registry.ts +8 -2
- package/src/cli/models-cli.ts +19 -0
- package/src/cli/update-cli.test.ts +28 -0
- package/src/cli/update-cli.ts +35 -8
- package/src/cli/usage-cli.ts +34 -5
- package/src/commands/update.ts +8 -2
- package/src/config/config-file.ts +6 -6
- package/src/config/settings-schema.ts +10 -0
- package/src/dap/client.ts +134 -36
- package/src/edit/file-snapshot-store.ts +12 -1
- package/src/edit/hashline/diff.ts +4 -13
- package/src/edit/hashline/execute.ts +1 -1
- package/src/edit/hashline/params.ts +5 -12
- package/src/edit/renderer.ts +4 -2
- package/src/edit/streaming.ts +15 -5
- package/src/export/html/tool-views.generated.js +2 -2
- package/src/extensibility/plugins/installer.ts +12 -3
- package/src/extensibility/plugins/manager.ts +32 -8
- package/src/extensibility/plugins/parser.ts +7 -5
- package/src/extensibility/tool-event-input.ts +1 -1
- package/src/hindsight/client.test.ts +33 -0
- package/src/hindsight/client.ts +42 -22
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/main.ts +7 -1
- package/src/mcp/oauth-flow.ts +93 -4
- package/src/mcp/smithery-auth.ts +3 -0
- package/src/mcp/smithery-connect.ts +9 -0
- package/src/mcp/smithery-registry.test.ts +51 -0
- package/src/mcp/smithery-registry.ts +27 -4
- package/src/modes/components/advisor-message.ts +13 -10
- package/src/modes/components/status-line/component.ts +11 -4
- package/src/modes/components/tool-execution.ts +74 -8
- package/src/modes/components/transcript-container.ts +26 -0
- package/src/modes/controllers/event-controller.ts +7 -3
- package/src/modes/controllers/tool-args-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/rpc/rpc-client.ts +29 -16
- package/src/modes/theme/shimmer.ts +49 -15
- package/src/modes/theme/theme.ts +7 -0
- package/src/session/agent-session.ts +166 -30
- package/src/session/indexed-session-storage.ts +40 -3
- package/src/session/session-manager.ts +83 -14
- package/src/session/session-storage.ts +112 -26
- package/src/slash-commands/helpers/usage-report.ts +6 -1
- package/src/ssh/__tests__/connection-manager-timeout.test.ts +61 -0
- package/src/ssh/__tests__/sshfs-mount.test.ts +13 -0
- package/src/ssh/connection-manager.ts +44 -11
- package/src/ssh/sshfs-mount.ts +27 -4
- package/src/subprocess/worker-client.ts +161 -10
- package/src/tools/bash.ts +30 -1
- package/src/tools/grep.ts +21 -1
- package/src/tools/read.ts +14 -4
- package/src/tools/renderers.ts +13 -0
- package/src/tools/ssh.ts +8 -0
- package/src/utils/clipboard.ts +49 -12
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/web/search/index.ts +8 -0
- package/src/web/search/providers/base.ts +1 -0
- package/src/web/search/providers/gemini.ts +23 -6
|
@@ -180,8 +180,7 @@ function resolvePreviewEdits(args: {
|
|
|
180
180
|
}): readonly Edit[] {
|
|
181
181
|
const { section, absolutePath, normalized, snapshots, expected, liveMatches, edits } = args;
|
|
182
182
|
if (!hasBlockEdit(edits)) return edits;
|
|
183
|
-
const baseText =
|
|
184
|
-
expected === undefined || liveMatches ? normalized : snapshots.byHashExact(absolutePath, expected)?.text;
|
|
183
|
+
const baseText = expected === undefined || liveMatches ? normalized : snapshots.byHash(absolutePath, expected)?.text;
|
|
185
184
|
if (baseText === undefined) {
|
|
186
185
|
throw createMismatchError(section, absolutePath, normalized, snapshots, expected ?? "");
|
|
187
186
|
}
|
|
@@ -200,17 +199,9 @@ function applyPreviewEdits(args: {
|
|
|
200
199
|
if (!options.skipHashValidation && expected === undefined) {
|
|
201
200
|
throw new Error(missingSnapshotTagMessage(section.path));
|
|
202
201
|
}
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
// the store retains text for the tag, require it to be unambiguous and
|
|
207
|
-
// byte-identical to live; otherwise fall through to recovery/reject below
|
|
208
|
-
// exactly as if the hash had not matched.
|
|
209
|
-
const liveMatches =
|
|
210
|
-
expected !== undefined &&
|
|
211
|
-
computeFileHash(normalized) === expected &&
|
|
212
|
-
(snapshots.byHash(absolutePath, expected) === null ||
|
|
213
|
-
snapshots.byHashExact(absolutePath, expected)?.text === normalized);
|
|
202
|
+
// The 4-hex tag is content-derived: when the live text hashes to it, trust
|
|
203
|
+
// the match and preview directly (mirrors Patcher's apply-time behavior).
|
|
204
|
+
const liveMatches = expected !== undefined && computeFileHash(normalized) === expected;
|
|
214
205
|
const edits = parsePreviewEdits(section, options.streaming);
|
|
215
206
|
const resolved = resolvePreviewEdits({ section, absolutePath, normalized, snapshots, expected, liveMatches, edits });
|
|
216
207
|
if (options.skipHashValidation || expected === undefined || liveMatches) return applyEdits(normalized, resolved);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coding-agent runner that drives the hashline {@link Patcher} on behalf of
|
|
3
|
-
* the `edit` tool. Converts
|
|
3
|
+
* the `edit` tool. Converts an `{input}` tool-call payload into a
|
|
4
4
|
* fully-applied patch, wraps the result in the agent's
|
|
5
5
|
* {@link AgentToolResult} shape, and attaches LSP diagnostics + `outputMeta`
|
|
6
6
|
* for the renderer.
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Arktype schema for the `edit` tool's hashline mode payload. The schema is
|
|
3
3
|
* deliberately permissive (allows extra keys) so providers can attach extra
|
|
4
|
-
* keys without rejection; only `input` is required.
|
|
5
|
-
* provider-emitted alias for `input`.
|
|
4
|
+
* keys without rejection; only `input` is required.
|
|
6
5
|
*/
|
|
7
6
|
import { type } from "arktype";
|
|
8
7
|
|
|
9
|
-
const
|
|
10
|
-
|
|
8
|
+
export const hashlineEditParamsSchema = type({
|
|
9
|
+
input: "string",
|
|
10
|
+
});
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
.pipe(raw => {
|
|
14
|
-
if (raw.input !== undefined || raw._input === undefined) return raw;
|
|
15
|
-
return { ...raw, input: raw._input };
|
|
16
|
-
})
|
|
17
|
-
.pipe(requiredInputSchema);
|
|
18
|
-
|
|
19
|
-
export type HashlineParams = Parameters<typeof hashlineEditParamsSchema.assert>[0];
|
|
12
|
+
export type HashlineParams = typeof hashlineEditParamsSchema.infer;
|
package/src/edit/renderer.ts
CHANGED
|
@@ -114,6 +114,7 @@ interface EditRenderArgs {
|
|
|
114
114
|
newText?: string;
|
|
115
115
|
patch?: string;
|
|
116
116
|
input?: string;
|
|
117
|
+
_input?: string;
|
|
117
118
|
all?: boolean;
|
|
118
119
|
// Patch mode fields
|
|
119
120
|
op?: Operation;
|
|
@@ -600,10 +601,11 @@ function getHashlineInputRenderSummary(
|
|
|
600
601
|
args: EditRenderArgs,
|
|
601
602
|
editMode: EditMode | undefined,
|
|
602
603
|
): HashlineInputRenderSummary | undefined {
|
|
603
|
-
|
|
604
|
+
const input = args.input ?? args._input;
|
|
605
|
+
if (editMode !== "hashline" || typeof input !== "string") {
|
|
604
606
|
return undefined;
|
|
605
607
|
}
|
|
606
|
-
return { entries: getHashlineInputSections(
|
|
608
|
+
return { entries: getHashlineInputSections(input) };
|
|
607
609
|
}
|
|
608
610
|
|
|
609
611
|
function getApplyPatchRenderSummary(
|
package/src/edit/streaming.ts
CHANGED
|
@@ -429,9 +429,19 @@ const patchStrategy: EditStreamingStrategy<PatchArgs> = {
|
|
|
429
429
|
|
|
430
430
|
interface HashlineArgs {
|
|
431
431
|
input?: string;
|
|
432
|
+
_input?: string;
|
|
432
433
|
__partialJson?: string;
|
|
433
434
|
}
|
|
434
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Text payload of a hashline edit call. The public schema declares `input`, but
|
|
438
|
+
* streaming sees the raw model output before validation coerces aliases, so a
|
|
439
|
+
* provider that emits the legacy `_input` key still previews correctly.
|
|
440
|
+
*/
|
|
441
|
+
function hashlineEditText(args: HashlineArgs | undefined): string | undefined {
|
|
442
|
+
return args?.input ?? args?._input;
|
|
443
|
+
}
|
|
444
|
+
|
|
435
445
|
/**
|
|
436
446
|
* While streaming a free-form text payload (apply_patch envelope, hashline
|
|
437
447
|
* input), trim the trailing partial line so per-character growth of an
|
|
@@ -513,7 +523,8 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
513
523
|
return args;
|
|
514
524
|
},
|
|
515
525
|
async computeDiffPreview(args, ctx) {
|
|
516
|
-
|
|
526
|
+
const input = hashlineEditText(args);
|
|
527
|
+
if (typeof input !== "string" || input.length === 0) return null;
|
|
517
528
|
// Unlike apply_patch, hashline previews flow through `applyPartialTo`,
|
|
518
529
|
// whose streaming-tolerant parser (`parsePatchStreaming` → `endStreaming`)
|
|
519
530
|
// drops a payload-less trailing op and projects a partially-typed payload
|
|
@@ -521,7 +532,6 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
521
532
|
// would instead strip the sole payload of a single-op `replace`/`insert`
|
|
522
533
|
// for almost the entire stream, collapsing the preview to "No changes" and
|
|
523
534
|
// rendering a blank box. Feed the raw in-flight text straight through.
|
|
524
|
-
const input = args.input;
|
|
525
535
|
ctx.signal.throwIfAborted();
|
|
526
536
|
|
|
527
537
|
let sections: readonly HashlineInputSection[];
|
|
@@ -579,19 +589,19 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
579
589
|
return "";
|
|
580
590
|
},
|
|
581
591
|
matcherDigest(args) {
|
|
582
|
-
const input = args
|
|
592
|
+
const input = hashlineEditText(args);
|
|
583
593
|
if (typeof input !== "string") return undefined;
|
|
584
594
|
// Body rows are `+TEXT`; headers and op lines are grammar, never content.
|
|
585
595
|
return extractAddedLines(input, false);
|
|
586
596
|
},
|
|
587
597
|
matcherPaths(args) {
|
|
588
|
-
const input = args
|
|
598
|
+
const input = hashlineEditText(args);
|
|
589
599
|
if (typeof input !== "string" || input.length === 0) return undefined;
|
|
590
600
|
const paths = extractHashlineHeaderPaths(input);
|
|
591
601
|
return paths.length > 0 ? paths : undefined;
|
|
592
602
|
},
|
|
593
603
|
matcherEntries(args) {
|
|
594
|
-
const input = args
|
|
604
|
+
const input = hashlineEditText(args);
|
|
595
605
|
if (typeof input !== "string" || input.length === 0) return undefined;
|
|
596
606
|
const entries = splitHashlinePerFile(input);
|
|
597
607
|
return entries.length > 0 ? entries : undefined;
|
|
@@ -17,9 +17,9 @@ Error generating stack: `+X.message+`
|
|
|
17
17
|
`),maxLines:6,title:N>D.length?`parse issues (${N} total)`:"parse issues"}),W0.jsx(S,{result:Y,maxLines:12})]})}var HH={Summary:jq,Body:xq};var K0=K($(),1),yq=/^[\w@%+=:,./-]+$/,gq=/\[raw output: artifact:\/\/([\w-]+)\]/;function mq(f){let Y=[];for(let Z in f){let X=$Z(f[Z]);Y.push(`${Z}=${yq.test(X)?X:JSON.stringify(X)}`)}return Y.join(" ")}function uq(f){if(!f||!T(f.async))return null;let Y=A(f.async.state);return Y?{state:Y,jobId:A(f.async.jobId)}:null}function cq({args:f,result:Y}){let Z=f.command===void 0?"…":A(f.command);if(Z===null)return K0.jsx(v,{what:"command"});let X=I(b(Z)||"…",80);return Y?.isError?K0.jsx("span",{className:"tv-err-text",children:X}):K0.jsx("span",{children:X})}function dq({args:f,result:Y}){let Z=f.command===void 0?"…":A(f.command),X=T(f.env)?mq(f.env):"",G=A(f.cwd),z=O(f.head),J=O(f.tail),U=P(Y),H=O(U?.exitCode),W=O(U?.wallTimeMs),Q=O(f.timeout)??O(U?.timeoutSeconds),_=O(U?.requestedTimeoutSeconds),q=uq(U),M=gq.exec(o0(Y))?.[1]??null,D=[];if(W!==null)D.push(W<1000?`wall ${Math.round(W)}ms`:`wall ${(W/1000).toFixed(1)}s`);if(_!==null&&_!==Q)D.push(`requested timeout ${_}s clamped`);if(q?.jobId)D.push(`job ${q.jobId}`);if(M)D.push(`artifact ${M}`);return K0.jsxs(K0.Fragment,{children:[K0.jsxs("div",{className:"tv-cmd",children:[K0.jsx("span",{className:"tv-cmd-prompt",children:"$"}),K0.jsxs("span",{className:"tv-cmd-text",children:[X&&K0.jsx("span",{className:"tv-cmd-env",children:`${X} `}),Z??K0.jsx(v,{what:"command"})]})]}),K0.jsx(o,{items:[G&&K0.jsxs(w,{children:["cwd=",V0(G)]}),Q!==null&&K0.jsxs(w,{children:["timeout=",Q,"s"]}),f.pty===!0&&K0.jsx(w,{tone:"accent",children:"pty"}),!q&&f.async===!0&&K0.jsx(w,{tone:"accent",children:"async"}),z!==null&&K0.jsxs(w,{children:["head=",z]}),J!==null&&K0.jsxs(w,{children:["tail=",J]}),H!==null&&K0.jsxs(w,{tone:"err",children:["exit ",H]}),q&&K0.jsxs(w,{tone:q.state==="failed"?"err":q.state==="running"?"accent":"ok",children:["async ",q.state]})]}),K0.jsx(q7,{result:Y}),K0.jsx(S,{result:Y,maxLines:12}),D.length>0&&K0.jsx(m,{children:D.join(" · ")})]})}var AH={Summary:cq,Body:dq};var v0=K($(),1);function WH(f){let Y=P(f);return{action:Y?A(Y.action):null,name:Y?A(Y.name):null,url:Y?A(Y.url):null,browser:Y?A(Y.browser):null}}function lq(f){if(!T(f.app))return null;return{path:A(f.app.path),cdpUrl:A(f.app.cdp_url),target:A(f.app.target)}}function pq(f){switch(f){case"open":return"ok";case"run":return"accent";case"close":return"warn";default:return}}function iq(f,Y){if(f?.cdpUrl)return`connected ${f.cdpUrl}`;if(f?.path)return`spawned ${V0(f.path)}`;return Y.browser}function oq({args:f,result:Y}){let Z=WH(Y),X=A(f.action)??Z.action??"?",G=X==="close"&&(f.all===!0||A(f.name)===null&&Z.name===null),z=Z.name??A(f.name)??"main",J=Z.url??A(f.url);return v0.jsxs(v0.Fragment,{children:[v0.jsx(w,{tone:pq(X),children:X}),v0.jsx("span",{children:G?"all tabs":z}),f.kill===!0&&v0.jsx(w,{tone:"err",children:"kill"}),J&&v0.jsx("span",{className:"tv-faint",children:I(V0(J),72)})]})}function nq({args:f,result:Y}){let Z=WH(Y),X=A(f.action)??Z.action,G=lq(f),z=Z.name??A(f.name),J=Z.url??A(f.url),U=iq(G,Z),H=T(f.viewport)?f.viewport:null,W=H?O(H.width):null,Q=H?O(H.height):null,_=H?O(H.scale):null,q=A(f.code);return v0.jsxs(v0.Fragment,{children:[v0.jsxs("span",{className:"tv-badges",children:[z!==null&&v0.jsxs(w,{children:["tab ",z]}),J&&v0.jsx(w,{tone:"accent",children:I(V0(J),120)}),U&&v0.jsx(w,{children:U}),G?.target&&v0.jsxs(w,{children:["target ",G.target]}),f.all===!0&&v0.jsx(w,{tone:"warn",children:"all"}),f.kill===!0&&v0.jsx(w,{tone:"err",children:"kill"}),W!==null&&Q!==null&&v0.jsxs(w,{children:[W,"×",Q,_!==null?`@${_}x`:""]})]}),X==="run"&&q!==null&&v0.jsx(O7,{code:q.replace(/\s+$/,""),lang:"javascript"}),v0.jsx(q7,{result:Y}),v0.jsx(S,{result:Y,maxLines:10})]})}var f8={Summary:oq,Body:nq};var G0=K($(),1);function rq(f){let Y=P(f);if(!Y||!T(Y.snapshot))return null;let Z=Y.snapshot,X=T(Z.source)?Z.source:null;return{id:A(Z.id)??(O(Z.id)!==null?String(Z.id):null),adapter:A(Z.adapter),status:A(Z.status),program:A(Z.program),stopReason:A(Z.stopReason),frameName:A(Z.frameName),sourcePath:X?A(X.path):null,line:O(Z.line),column:O(Z.column),exitCode:O(Z.exitCode),needsConfigurationDone:Z.needsConfigurationDone===!0}}function aq(f){let Y=A(f.args.action)??A(P(f.result)?.action);return Y?Y.replace(/_/g," "):"request"}function sq(f){return A(f.function)??A(f.expression)??A(f.command)??A(f.memory_reference)??A(f.instruction_reference)??A(f.data_id)??A(f.name)}function tq(f){let{args:Y}=f,Z=A(Y.program),X=A(Y.file),G=O(Y.line),z=sq(Y);return G0.jsxs(G0.Fragment,{children:[G0.jsx(w,{tone:"accent",children:aq(f)}),Z!==null?G0.jsx(Y0,{path:Z}):X!==null?G0.jsx(Y0,{path:X,from:G??void 0}):z!==null?G0.jsx("span",{children:I(b(z),80)}):null]})}var eq=[["adapter","adapter"],["cwd","cwd"],["function","function"],["name","name"],["condition","condition"],["hit_condition","hit condition"],["context","context"],["frame_id","frame id"],["scope_id","scope id"],["variable_ref","variable ref"],["pid","pid"],["host","host"],["port","port"],["levels","levels"],["memory_reference","memory ref"],["instruction_reference","instruction ref"],["instruction_count","instruction count"],["instruction_offset","instruction offset"],["offset","offset"],["count","count"],["data","data"],["data_id","data id"],["access_type","access"],["command","command"],["resolve_symbols","resolve symbols"],["allow_partial","allow partial"],["start_module","start module"],["module_count","module count"],["timeout","timeout"]];function fQ(f){let{args:Y,result:Z}=f,X=A(Y.program),G=A(Y.file),z=O(Y.line),J=A(Y.expression),U=Array.isArray(Y.args)?Y.args.filter((_)=>typeof _==="string"):[],H=[];if(X!==null)H.push(G0.jsx(E,{k:"program",children:G0.jsx(Y0,{path:X})},"program"));if(U.length>0)H.push(G0.jsx(E,{k:"args",children:I(U.join(" "),160)},"args"));if(G!==null)H.push(G0.jsx(E,{k:"file",children:G0.jsx(Y0,{path:G,from:z??void 0})},"file"));else if(z!==null)H.push(G0.jsx(E,{k:"line",children:z},"line"));for(let[_,q]of eq){let M=Y[_];if(typeof M!=="string"&&typeof M!=="number"&&typeof M!=="boolean")continue;H.push(G0.jsx(E,{k:q,children:I($Z(M),120)},_))}let W="";if(T(Y.arguments))try{W=JSON.stringify(Y.arguments,null,2)??""}catch{W=""}let Q=rq(Z);return G0.jsxs(G0.Fragment,{children:[H.length>0&&G0.jsx(O0,{children:H}),J!==null&&G0.jsx(O7,{code:J,title:"expression",maxLines:8}),W&&G0.jsx(O7,{code:W,lang:"json",title:"arguments",maxLines:10}),Q&&G0.jsxs(O0,{children:[Q.id!==null&&G0.jsx(E,{k:"session",children:Q.id}),Q.adapter!==null&&G0.jsx(E,{k:"adapter",children:Q.adapter}),Q.status!==null&&G0.jsx(E,{k:"status",children:G0.jsx(w,{tone:Q.status==="exited"?"warn":"ok",children:Q.status})}),Q.program!==null&&G0.jsx(E,{k:"program",children:G0.jsx(Y0,{path:Q.program})}),Q.stopReason!==null&&G0.jsx(E,{k:"stop reason",children:Q.stopReason}),Q.frameName!==null&&G0.jsx(E,{k:"frame",children:Q.frameName}),Q.sourcePath!==null&&Q.line!==null&&G0.jsx(E,{k:"location",children:G0.jsx(Y0,{path:Q.sourcePath,sel:Q.column!==null?`${Q.line}:${Q.column}`:String(Q.line)})}),Q.exitCode!==null&&G0.jsx(E,{k:"exit code",children:Q.exitCode}),Q.needsConfigurationDone&&G0.jsx(E,{k:"configuration",children:"pending configurationDone — set breakpoints, then continue"})]}),G0.jsx(S,{result:Z,maxLines:10})]})}var qH={Summary:tq,Body:fQ};var y=K($(),1);function YQ(f){let Y=f.trimEnd();if(!Y.startsWith("[")||!Y.endsWith("]"))return null;let Z=Y.slice(1,-1).trim(),X=/#[0-9a-fA-F]{4}$/.exec(Z);if(X)Z=Z.slice(0,X.index);if(Z.length>=2){let G=Z[0];if((G==='"'||G==="'")&&G===Z[Z.length-1])Z=Z.slice(1,-1)}return Z.length>0?Z:null}var ZQ=/^\*{3} (?:Update|Add|Delete) File:\s*(.+)$/;function RH(f){let Y=f.startsWith("\uFEFF")?f.slice(1):f,Z=[];for(let X of Y.split(`
|
|
18
18
|
`)){let G=X.replace(/\r$/,""),z=YQ(G);if(z){Z.push(z);continue}let J=ZQ.exec(G.trim());if(J)Z.push(J[1].trim())}return Z}var XQ=/^(?:replace|insert|delete)\b/;function GQ(f){let Y=0;for(let Z of f.split(`
|
|
19
19
|
`))if(XQ.test(Z))Y++;return Y}function zQ(f){let Y=0,Z=0;for(let X of f.split(`
|
|
20
|
-
`))if(X.startsWith("+"))Y++;else if(X.startsWith("-"))Z++;return{added:Y,removed:Z}}function QH(f){let Y=T(f.diagnostics)?f.diagnostics:null,Z=[];if(Y&&Array.isArray(Y.messages)){for(let X of Y.messages)if(typeof X==="string")Z.push(X)}return{path:A(f.path),diff:A(f.diff),firstChangedLine:O(f.firstChangedLine),op:A(f.op),move:A(f.move),isError:f.isError===!0,errorText:A(f.displayErrorText)??A(f.errorText),diagnostics:Y?{summary:A(Y.summary),messages:Z,errored:Y.errored===!0}:null}}function JQ({args:f,result:Y}){let Z=A(f.input),X=Z?RH(Z):[],G=A(f.file_path)??A(f.path);if(X.length===0&&G)X.push(G);if(X.length===0&&Array.isArray(f.edits))for(let Q of f.edits){let _=T(Q)?A(Q.path):null;if(_&&!X.includes(_))X.push(_)}let z=Z?GQ(Z):0,J=P(Y),U=J&&Y?.isError!==!0?A(J.diff):null,H=U?zQ(U):null,W=Z?I(b(Z.split(`
|
|
20
|
+
`))if(X.startsWith("+"))Y++;else if(X.startsWith("-"))Z++;return{added:Y,removed:Z}}function QH(f){let Y=T(f.diagnostics)?f.diagnostics:null,Z=[];if(Y&&Array.isArray(Y.messages)){for(let X of Y.messages)if(typeof X==="string")Z.push(X)}return{path:A(f.path),diff:A(f.diff),firstChangedLine:O(f.firstChangedLine),op:A(f.op),move:A(f.move),isError:f.isError===!0,errorText:A(f.displayErrorText)??A(f.errorText),diagnostics:Y?{summary:A(Y.summary),messages:Z,errored:Y.errored===!0}:null}}function JQ({args:f,result:Y}){let Z=A(f.input)??A(f._input),X=Z?RH(Z):[],G=A(f.file_path)??A(f.path);if(X.length===0&&G)X.push(G);if(X.length===0&&Array.isArray(f.edits))for(let Q of f.edits){let _=T(Q)?A(Q.path):null;if(_&&!X.includes(_))X.push(_)}let z=Z?GQ(Z):0,J=P(Y),U=J&&Y?.isError!==!0?A(J.diff):null,H=U?zQ(U):null,W=Z?I(b(Z.split(`
|
|
21
21
|
`,1)[0]??""),80):"";return y.jsxs(y.Fragment,{children:[X.length>0?y.jsx(Y0,{path:X[0]}):y.jsx("span",{children:W}),X.length>1&&y.jsxs(y.Fragment,{children:[" ",y.jsxs(w,{children:["+",X.length-1," more"]})]}),z>0&&y.jsxs(y.Fragment,{children:[" ",y.jsxs(w,{children:[z," op",z===1?"":"s"]})]}),H!==null&&H.added>0&&y.jsxs(y.Fragment,{children:[" ",y.jsxs(w,{tone:"ok",children:["+",H.added]})]}),H!==null&&H.removed>0&&y.jsxs(y.Fragment,{children:[" ",y.jsxs(w,{tone:"err",children:["−",H.removed]})]}),Y?.isError===!0&&y.jsxs(y.Fragment,{children:[" ",y.jsx(w,{tone:"err",children:"failed"})]})]})}function MH({entry:f,fallbackPath:Y}){let Z=f.path??Y??null,X=f.op==="create"||f.op==="delete"?f.op:null,G=f.diagnostics;return y.jsxs("div",{children:[(Z!==null||X!==null||f.move!==null)&&y.jsx("div",{className:"tv-row",children:y.jsxs("span",{className:"tv-row-val",children:[Z!==null&&y.jsx(Y0,{path:Z,from:f.isError?null:f.firstChangedLine}),f.move!==null&&y.jsxs(y.Fragment,{children:[" → ",y.jsx(Y0,{path:f.move})]}),X!==null&&y.jsxs(y.Fragment,{children:[" ",y.jsx(w,{tone:X==="delete"?"err":"ok",children:X})]}),f.isError&&y.jsxs(y.Fragment,{children:[" ",y.jsx(w,{tone:"err",children:"failed"})]})]})}),f.isError?f.errorText!==null&&y.jsx(n,{text:f.errorText,error:!0,maxLines:10}):f.diff!==null&&f.diff.length>0&&y.jsx(AX,{diff:f.diff,maxLines:40}),G?.summary&&y.jsx(p,{tone:G.errored?"err":"warn",children:G.summary}),G!==null&&G.messages.length>0&&y.jsx(n,{text:G.messages.join(`
|
|
22
|
-
`),maxLines:6})]})}function UQ({args:f,result:Y}){let Z=A(f.input),X=P(Y),G=[];if(X&&Array.isArray(X.perFileResults)){for(let U of X.perFileResults)if(T(U))G.push(QH(U))}let z=A(f.file_path)??A(f.path)??(Z?RH(Z)[0]??null:null),J;if(G.length>0)J=G.map((U,H)=>y.jsx(MH,{entry:U},`${U.path??""}:${H}`));else if(Y?.isError===!0)J=y.jsx(S,{result:Y,maxLines:15});else if(X){let U=QH(X);J=U.diff!==null||U.diagnostics!==null||U.move!==null?y.jsx(MH,{entry:U,fallbackPath:z}):y.jsx(S,{result:Y,maxLines:8})}else J=y.jsx(S,{result:Y,maxLines:8});return y.jsxs(y.Fragment,{children:[J,Array.isArray(f.edits)&&f.edits.length>0&&y.jsx(O0,{children:f.edits.map((U,H)=>T(U)?y.jsx(E,{k:A(U.op)??"edit",children:A(U.sel)??A(U.path)??A(U.rename)??A(U.move)??"?"},`${H}`):y.jsx(E,{k:"edit",children:y.jsx(v,{what:"edit"})},`${H}`))}),Z!==null&&Z.length>0&&y.jsx(n,{text:Z,variant:"code",maxLines:10,title:"input"}),Z===null&&f.input!==void 0&&y.jsx(v,{what:"input"})]})}var Y8={Summary:JQ,Body:UQ};var C0=K($(),1),HQ={py:"python",js:"javascript",ts:"typescript",rb:"ruby",jl:"julia"};function VY(f){let Y=(f??"").toUpperCase();if(Y==="PY"||Y==="PYTHON"||Y==="IPY"||Y==="IPYTHON")return"py";if(Y==="JS"||Y==="JAVASCRIPT")return"js";if(Y==="TS"||Y==="TYPESCRIPT")return"ts";if(Y==="RB"||Y==="RUBY")return"rb";if(Y==="JL"||Y==="JULIA")return"jl";return null}function AQ(f){let Y=[],Z=0;while(Z<f.length){while(Z<f.length&&/\s/.test(f[Z]))Z++;if(Z>=f.length)break;let X="";while(Z<f.length&&!/\s/.test(f[Z])){let G=f[Z];if(G==='"'||G==="'"){X+=G,Z++;while(Z<f.length&&f[Z]!==G)X+=f[Z],Z++;if(Z<f.length)X+=f[Z],Z++}else X+=G,Z++}Y.push(X)}return Y}function WQ(f){let Y=/^\*{2,}\s*Cell\b\s*(.*)$/i,Z=/^\*{2,}\s*End\b.*$/i,X=/^([a-zA-Z][\w-]*)(?::(?:"([^"]*)"|'([^']*)'|(.*)))?$/,G=/^\d+(?:ms|s|m)?$/,z=["id","title","name","cell","file","label"],J=["t","timeout","duration","time"],U=["rst","reset"],H=f.split(`
|
|
22
|
+
`),maxLines:6})]})}function UQ({args:f,result:Y}){let Z=A(f.input)??A(f._input),X=P(Y),G=[];if(X&&Array.isArray(X.perFileResults)){for(let U of X.perFileResults)if(T(U))G.push(QH(U))}let z=A(f.file_path)??A(f.path)??(Z?RH(Z)[0]??null:null),J;if(G.length>0)J=G.map((U,H)=>y.jsx(MH,{entry:U},`${U.path??""}:${H}`));else if(Y?.isError===!0)J=y.jsx(S,{result:Y,maxLines:15});else if(X){let U=QH(X);J=U.diff!==null||U.diagnostics!==null||U.move!==null?y.jsx(MH,{entry:U,fallbackPath:z}):y.jsx(S,{result:Y,maxLines:8})}else J=y.jsx(S,{result:Y,maxLines:8});return y.jsxs(y.Fragment,{children:[J,Array.isArray(f.edits)&&f.edits.length>0&&y.jsx(O0,{children:f.edits.map((U,H)=>T(U)?y.jsx(E,{k:A(U.op)??"edit",children:A(U.sel)??A(U.path)??A(U.rename)??A(U.move)??"?"},`${H}`):y.jsx(E,{k:"edit",children:y.jsx(v,{what:"edit"})},`${H}`))}),Z!==null&&Z.length>0&&y.jsx(n,{text:Z,variant:"code",maxLines:10,title:"input"}),Z===null&&(f.input!==void 0||f._input!==void 0)&&y.jsx(v,{what:"input"})]})}var Y8={Summary:JQ,Body:UQ};var C0=K($(),1),HQ={py:"python",js:"javascript",ts:"typescript",rb:"ruby",jl:"julia"};function VY(f){let Y=(f??"").toUpperCase();if(Y==="PY"||Y==="PYTHON"||Y==="IPY"||Y==="IPYTHON")return"py";if(Y==="JS"||Y==="JAVASCRIPT")return"js";if(Y==="TS"||Y==="TYPESCRIPT")return"ts";if(Y==="RB"||Y==="RUBY")return"rb";if(Y==="JL"||Y==="JULIA")return"jl";return null}function AQ(f){let Y=[],Z=0;while(Z<f.length){while(Z<f.length&&/\s/.test(f[Z]))Z++;if(Z>=f.length)break;let X="";while(Z<f.length&&!/\s/.test(f[Z])){let G=f[Z];if(G==='"'||G==="'"){X+=G,Z++;while(Z<f.length&&f[Z]!==G)X+=f[Z],Z++;if(Z<f.length)X+=f[Z],Z++}else X+=G,Z++}Y.push(X)}return Y}function WQ(f){let Y=/^\*{2,}\s*Cell\b\s*(.*)$/i,Z=/^\*{2,}\s*End\b.*$/i,X=/^([a-zA-Z][\w-]*)(?::(?:"([^"]*)"|'([^']*)'|(.*)))?$/,G=/^\d+(?:ms|s|m)?$/,z=["id","title","name","cell","file","label"],J=["t","timeout","duration","time"],U=["rst","reset"],H=f.split(`
|
|
23
23
|
`);if(H.length>0&&H[H.length-1]==="")H.pop();let W=[],Q=0;while(Q<H.length&&H[Q].trim()==="")Q++;while(Q<H.length){let _=Y.exec(H[Q]);if(!_){Q++;continue}let q=AQ(_[1]??""),M=null,D="",N=[],u=!1,B=[];for(let L of q){if(U.includes(L.toLowerCase())){u=!0;continue}let V=X.exec(L);if(V&&L.includes(":")){let r=V[1].toLowerCase(),C=V[2]??V[3]??V[4]??"",d=VY(r);if(d){if(!M)M=d;if(!D&&C)D=C;continue}if(z.includes(r)){if(!D)D=C;continue}if(J.includes(r)){N.push(`t=${C}`);continue}if(U.includes(r))N.push("rst");continue}let h=VY(L);if(h&&!M){M=h;continue}if(G.test(L)){N.push(`t=${L}`);continue}B.push(L)}if(!D&&B.length>0)D=B.join(" ");if(u)N.push("rst");Q++;let R=[];while(Q<H.length){if(Z.test(H[Q])){Q++;break}if(Y.test(H[Q]))break;R.push(H[Q]),Q++}while(R.length>0&&R[R.length-1].trim()==="")R.pop();W.push({lang:M??"py",title:D,attrs:N,code:R.join(`
|
|
24
24
|
`)});while(Q<H.length&&H[Q].trim()==="")Q++}return W}function qQ(f){let Y=/^\*{2,}\s*Begin\b\s*(\S+)?\s*$/i,Z=/^\*{2,}\s*End\b.*$/i,X=/^\*{2,}\s*Title\s*:\s*(.+?)\s*$/i,G=/^\*{2,}\s*Timeout\s*:\s*(\S+)\s*$/i,z=/^\*{2,}\s*Reset\s*$/i,J=f.split(`
|
|
25
25
|
`);if(J.length>0&&J[J.length-1]==="")J.pop();let U=[],H=0;while(H<J.length&&J[H].trim()==="")H++;while(H<J.length){let W=Y.exec(J[H]);if(!W){H++;continue}let Q=VY(W[1])??"py";H++;let _="",q=[];while(H<J.length){let D=X.exec(J[H]);if(D){if(!_)_=D[1];H++;continue}let N=G.exec(J[H]);if(N){q.push(`t=${N[1]}`),H++;continue}if(z.test(J[H])){q.push("rst"),H++;continue}break}let M=[];while(H<J.length){if(Z.test(J[H])){H++;break}if(Y.test(J[H]))break;M.push(J[H]),H++}while(M.length>0&&M[M.length-1].trim()==="")M.pop();U.push({lang:Q,title:_,attrs:q,code:M.join(`
|
|
@@ -53,9 +53,14 @@ export async function installPlugin(packageName: string): Promise<InstalledPlugi
|
|
|
53
53
|
windowsHide: true,
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
// Drain both pipes concurrently with proc.exited to avoid a pipe-buffer
|
|
57
|
+
// deadlock if bun install floods stdout/stderr.
|
|
58
|
+
const [exitCode, , stderr] = await Promise.all([
|
|
59
|
+
proc.exited,
|
|
60
|
+
new Response(proc.stdout).text(),
|
|
61
|
+
new Response(proc.stderr).text(),
|
|
62
|
+
]);
|
|
57
63
|
if (exitCode !== 0) {
|
|
58
|
-
const stderr = await new Response(proc.stderr).text();
|
|
59
64
|
throw new Error(`Failed to install ${packageName}: ${stderr}`);
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -95,7 +100,11 @@ export async function uninstallPlugin(name: string): Promise<void> {
|
|
|
95
100
|
windowsHide: true,
|
|
96
101
|
});
|
|
97
102
|
|
|
98
|
-
const exitCode = await
|
|
103
|
+
const [exitCode] = await Promise.all([
|
|
104
|
+
proc.exited,
|
|
105
|
+
new Response(proc.stdout).text(),
|
|
106
|
+
new Response(proc.stderr).text(),
|
|
107
|
+
]);
|
|
99
108
|
if (exitCode !== 0) {
|
|
100
109
|
throw new Error(`Failed to uninstall ${name}`);
|
|
101
110
|
}
|
|
@@ -458,10 +458,17 @@ export class PluginManager {
|
|
|
458
458
|
stderr: "pipe",
|
|
459
459
|
windowsHide: true,
|
|
460
460
|
});
|
|
461
|
-
|
|
461
|
+
// Drain stdout+stderr concurrently with proc.exited. Awaiting exited
|
|
462
|
+
// before reading either pipe risks a >64 KiB OS-pipe-buffer deadlock
|
|
463
|
+
// once bun install prints enough progress; even where Bun currently
|
|
464
|
+
// buffers eagerly, doing this leaks unbounded memory.
|
|
465
|
+
const [installExit, , installStderr] = await Promise.all([
|
|
466
|
+
installProc.exited,
|
|
467
|
+
new Response(installProc.stdout).text(),
|
|
468
|
+
new Response(installProc.stderr).text(),
|
|
469
|
+
]);
|
|
462
470
|
if (installExit !== 0) {
|
|
463
|
-
|
|
464
|
-
throw new Error(`bun install failed: ${stderr}`);
|
|
471
|
+
throw new Error(`bun install failed: ${installStderr}`);
|
|
465
472
|
}
|
|
466
473
|
// Resolve actual package name. npm specs encode the name (strip version);
|
|
467
474
|
// git specs do not, so diff plugins/package.json deps to find the new entry.
|
|
@@ -508,10 +515,14 @@ export class PluginManager {
|
|
|
508
515
|
stderr: "pipe",
|
|
509
516
|
windowsHide: true,
|
|
510
517
|
});
|
|
511
|
-
|
|
518
|
+
// Same drain-concurrent-with-exit pattern as the bun install above.
|
|
519
|
+
const [updateExit, , updateStderr] = await Promise.all([
|
|
520
|
+
updateProc.exited,
|
|
521
|
+
new Response(updateProc.stdout).text(),
|
|
522
|
+
new Response(updateProc.stderr).text(),
|
|
523
|
+
]);
|
|
512
524
|
if (updateExit !== 0) {
|
|
513
|
-
|
|
514
|
-
throw new Error(`bun update ${actualName} failed: ${stderr}`);
|
|
525
|
+
throw new Error(`bun update ${actualName} failed: ${updateStderr}`);
|
|
515
526
|
}
|
|
516
527
|
}
|
|
517
528
|
|
|
@@ -608,7 +619,13 @@ export class PluginManager {
|
|
|
608
619
|
windowsHide: true,
|
|
609
620
|
});
|
|
610
621
|
|
|
611
|
-
|
|
622
|
+
// Drain both pipes concurrently with proc.exited to avoid a pipe-buffer
|
|
623
|
+
// deadlock if bun uninstall floods stdout/stderr.
|
|
624
|
+
const [exitCode] = await Promise.all([
|
|
625
|
+
proc.exited,
|
|
626
|
+
new Response(proc.stdout).text(),
|
|
627
|
+
new Response(proc.stderr).text(),
|
|
628
|
+
]);
|
|
612
629
|
if (exitCode !== 0) {
|
|
613
630
|
throw new Error(`npm uninstall failed for ${name}`);
|
|
614
631
|
}
|
|
@@ -1007,7 +1024,14 @@ export class PluginManager {
|
|
|
1007
1024
|
stderr: "pipe",
|
|
1008
1025
|
windowsHide: true,
|
|
1009
1026
|
});
|
|
1010
|
-
|
|
1027
|
+
// Drain pipes concurrently with proc.exited; otherwise a chatty
|
|
1028
|
+
// bun install can block on a full OS pipe buffer.
|
|
1029
|
+
const [exit] = await Promise.all([
|
|
1030
|
+
proc.exited,
|
|
1031
|
+
new Response(proc.stdout).text(),
|
|
1032
|
+
new Response(proc.stderr).text(),
|
|
1033
|
+
]);
|
|
1034
|
+
return exit === 0;
|
|
1011
1035
|
} catch {
|
|
1012
1036
|
return false;
|
|
1013
1037
|
}
|
|
@@ -86,20 +86,22 @@ export function formatPluginSpec(spec: ParsedPluginSpec): string {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
* Extract the
|
|
89
|
+
* Extract the dependency key from an npm package specifier.
|
|
90
90
|
* Used for path lookups after npm install.
|
|
91
91
|
*
|
|
92
92
|
* @example
|
|
93
93
|
* extractPackageName("lodash@4.17.21") // "lodash"
|
|
94
94
|
* extractPackageName("@scope/pkg@1.0.0") // "@scope/pkg"
|
|
95
95
|
* extractPackageName("@scope/pkg") // "@scope/pkg"
|
|
96
|
+
* extractPackageName("npm:lodash") // "lodash"
|
|
96
97
|
*/
|
|
97
98
|
export function extractPackageName(specifier: string): string {
|
|
99
|
+
const npmSpecifier = specifier.replace(/^npm:/i, "");
|
|
98
100
|
// Handle scoped packages: @scope/name@version -> @scope/name
|
|
99
|
-
if (
|
|
100
|
-
const match =
|
|
101
|
-
return match ? match[1] :
|
|
101
|
+
if (npmSpecifier.startsWith("@")) {
|
|
102
|
+
const match = npmSpecifier.match(/^(@[^/]+\/[^@]+)/);
|
|
103
|
+
return match ? match[1] : npmSpecifier;
|
|
102
104
|
}
|
|
103
105
|
// Unscoped: name@version -> name
|
|
104
|
-
return
|
|
106
|
+
return npmSpecifier.replace(/@[^@]+$/, "");
|
|
105
107
|
}
|
|
@@ -60,7 +60,7 @@ export function normalizeToolEventInput(toolName: string, input: Record<string,
|
|
|
60
60
|
if (toolName !== "edit" || stringField(input, "path")) return input;
|
|
61
61
|
|
|
62
62
|
// Hashline edit mode: the only authoritative target list is the parsed
|
|
63
|
-
// `¶PATH#TAG` headers inside
|
|
63
|
+
// `¶PATH#TAG` headers inside the patch. Trusting a passthrough
|
|
64
64
|
// `_path` here would let a model-supplied field override the real edit
|
|
65
65
|
// target and bypass extension gates that allowlist by path.
|
|
66
66
|
const rawInput = stringField(input, "input") ?? stringField(input, "_input");
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
2
|
+
import { HindsightApi } from "./client";
|
|
3
|
+
|
|
4
|
+
type FetchInput = string | URL | Request;
|
|
5
|
+
type FetchInit = RequestInit | BunFetchRequestInit;
|
|
6
|
+
|
|
7
|
+
describe("HindsightApi fetch cancellation", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.restoreAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("combines caller cancellation with the request timeout", async () => {
|
|
13
|
+
let requestSignal: AbortSignal | undefined;
|
|
14
|
+
const fetchStub = Object.assign(
|
|
15
|
+
async (_input: FetchInput, init?: FetchInit) => {
|
|
16
|
+
requestSignal = init?.signal ?? undefined;
|
|
17
|
+
return Response.json({ results: [] });
|
|
18
|
+
},
|
|
19
|
+
{ preconnect: globalThis.fetch.preconnect },
|
|
20
|
+
);
|
|
21
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(fetchStub);
|
|
22
|
+
|
|
23
|
+
const caller = new AbortController();
|
|
24
|
+
const client = new HindsightApi({ baseUrl: "https://hindsight.example" });
|
|
25
|
+
await client.recall("bank", "query", { signal: caller.signal });
|
|
26
|
+
|
|
27
|
+
expect(requestSignal).toBeInstanceOf(AbortSignal);
|
|
28
|
+
expect(requestSignal).not.toBe(caller.signal);
|
|
29
|
+
caller.abort(new Error("caller aborted"));
|
|
30
|
+
expect(requestSignal?.aborted).toBe(true);
|
|
31
|
+
expect(requestSignal?.reason).toBe(caller.signal.reason);
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/hindsight/client.ts
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* tests to spy on.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { isTimeoutError, withTimeoutSignal } from "../utils/fetch-timeout";
|
|
11
12
|
import type { HindsightConfig } from "./config";
|
|
12
13
|
|
|
13
14
|
const USER_AGENT = "oh-my-pi-coding-agent";
|
|
14
15
|
const DEFAULT_USER_AGENT = USER_AGENT;
|
|
16
|
+
const HINDSIGHT_REQUEST_TIMEOUT_MS = 30_000;
|
|
15
17
|
|
|
16
18
|
export type Budget = "low" | "mid" | "high" | string;
|
|
17
19
|
export type TagsMatch = "any" | "all" | "any_strict" | "all_strict";
|
|
@@ -24,6 +26,11 @@ export interface HindsightApiOptions {
|
|
|
24
26
|
userAgent?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/** Caller cancellation shared by Hindsight request option bags. */
|
|
30
|
+
export interface HindsightRequestOptions {
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export interface RecallResult {
|
|
28
35
|
id?: string;
|
|
29
36
|
text: string;
|
|
@@ -77,7 +84,7 @@ export interface MemoryItemInput {
|
|
|
77
84
|
updateMode?: UpdateMode;
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
export interface RetainOptions {
|
|
87
|
+
export interface RetainOptions extends HindsightRequestOptions {
|
|
81
88
|
timestamp?: Date | string;
|
|
82
89
|
context?: string;
|
|
83
90
|
metadata?: Record<string, string>;
|
|
@@ -87,7 +94,7 @@ export interface RetainOptions {
|
|
|
87
94
|
updateMode?: UpdateMode;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
export interface RetainBatchOptions {
|
|
97
|
+
export interface RetainBatchOptions extends HindsightRequestOptions {
|
|
91
98
|
/** Document id applied to every item that doesn't carry its own. */
|
|
92
99
|
documentId?: string;
|
|
93
100
|
/** Tags attached to the resulting document(s), not individual items. */
|
|
@@ -95,7 +102,7 @@ export interface RetainBatchOptions {
|
|
|
95
102
|
async?: boolean;
|
|
96
103
|
}
|
|
97
104
|
|
|
98
|
-
export interface RecallOptions {
|
|
105
|
+
export interface RecallOptions extends HindsightRequestOptions {
|
|
99
106
|
types?: string[];
|
|
100
107
|
maxTokens?: number;
|
|
101
108
|
budget?: Budget;
|
|
@@ -103,19 +110,19 @@ export interface RecallOptions {
|
|
|
103
110
|
tagsMatch?: TagsMatch;
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
export interface ReflectOptions {
|
|
113
|
+
export interface ReflectOptions extends HindsightRequestOptions {
|
|
107
114
|
context?: string;
|
|
108
115
|
budget?: Budget;
|
|
109
116
|
tags?: string[];
|
|
110
117
|
tagsMatch?: TagsMatch;
|
|
111
118
|
}
|
|
112
119
|
|
|
113
|
-
export interface CreateBankOptions {
|
|
120
|
+
export interface CreateBankOptions extends HindsightRequestOptions {
|
|
114
121
|
reflectMission?: string;
|
|
115
122
|
retainMission?: string;
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
export interface ListMemoriesOptions {
|
|
125
|
+
export interface ListMemoriesOptions extends HindsightRequestOptions {
|
|
119
126
|
limit?: number;
|
|
120
127
|
offset?: number;
|
|
121
128
|
type?: string;
|
|
@@ -123,12 +130,12 @@ export interface ListMemoriesOptions {
|
|
|
123
130
|
consolidationState?: ConsolidationState;
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
export interface ListDocumentsOptions {
|
|
133
|
+
export interface ListDocumentsOptions extends HindsightRequestOptions {
|
|
127
134
|
limit?: number;
|
|
128
135
|
offset?: number;
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
export interface UpdateDocumentOptions {
|
|
138
|
+
export interface UpdateDocumentOptions extends HindsightRequestOptions {
|
|
132
139
|
tags?: string[];
|
|
133
140
|
}
|
|
134
141
|
|
|
@@ -166,7 +173,7 @@ export interface MentalModelHistoryEntry {
|
|
|
166
173
|
[key: string]: unknown;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
export interface CreateMentalModelOptions {
|
|
176
|
+
export interface CreateMentalModelOptions extends HindsightRequestOptions {
|
|
170
177
|
id?: string;
|
|
171
178
|
tags?: string[];
|
|
172
179
|
maxTokens?: number;
|
|
@@ -183,11 +190,11 @@ export interface RefreshMentalModelResponse {
|
|
|
183
190
|
[key: string]: unknown;
|
|
184
191
|
}
|
|
185
192
|
|
|
186
|
-
export interface ListMentalModelsOptions {
|
|
193
|
+
export interface ListMentalModelsOptions extends HindsightRequestOptions {
|
|
187
194
|
detail?: MentalModelDetail;
|
|
188
195
|
}
|
|
189
196
|
|
|
190
|
-
export interface GetMentalModelOptions {
|
|
197
|
+
export interface GetMentalModelOptions extends HindsightRequestOptions {
|
|
191
198
|
detail?: MentalModelDetail;
|
|
192
199
|
}
|
|
193
200
|
|
|
@@ -208,6 +215,7 @@ interface RequestOptions {
|
|
|
208
215
|
query?: Record<string, unknown>;
|
|
209
216
|
/** Return null instead of throwing on a 404 response. */
|
|
210
217
|
allow404?: boolean;
|
|
218
|
+
signal?: AbortSignal;
|
|
211
219
|
}
|
|
212
220
|
|
|
213
221
|
export class HindsightApi {
|
|
@@ -240,7 +248,10 @@ export class HindsightApi {
|
|
|
240
248
|
"POST",
|
|
241
249
|
`/v1/default/banks/${encodeURIComponent(bankId)}/memories`,
|
|
242
250
|
"retain",
|
|
243
|
-
{
|
|
251
|
+
{
|
|
252
|
+
body: { items: [item], async: options?.async },
|
|
253
|
+
signal: options?.signal,
|
|
254
|
+
},
|
|
244
255
|
);
|
|
245
256
|
}
|
|
246
257
|
|
|
@@ -270,6 +281,7 @@ export class HindsightApi {
|
|
|
270
281
|
document_tags: options?.documentTags,
|
|
271
282
|
async: options?.async,
|
|
272
283
|
},
|
|
284
|
+
signal: options?.signal,
|
|
273
285
|
},
|
|
274
286
|
);
|
|
275
287
|
}
|
|
@@ -288,6 +300,7 @@ export class HindsightApi {
|
|
|
288
300
|
tags: options?.tags,
|
|
289
301
|
tags_match: options?.tagsMatch,
|
|
290
302
|
},
|
|
303
|
+
signal: options?.signal,
|
|
291
304
|
},
|
|
292
305
|
);
|
|
293
306
|
}
|
|
@@ -305,6 +318,7 @@ export class HindsightApi {
|
|
|
305
318
|
tags: options?.tags,
|
|
306
319
|
tags_match: options?.tagsMatch,
|
|
307
320
|
},
|
|
321
|
+
signal: options?.signal,
|
|
308
322
|
},
|
|
309
323
|
);
|
|
310
324
|
}
|
|
@@ -319,6 +333,7 @@ export class HindsightApi {
|
|
|
319
333
|
reflect_mission: options.reflectMission,
|
|
320
334
|
retain_mission: options.retainMission,
|
|
321
335
|
},
|
|
336
|
+
signal: options.signal,
|
|
322
337
|
},
|
|
323
338
|
);
|
|
324
339
|
}
|
|
@@ -340,6 +355,7 @@ export class HindsightApi {
|
|
|
340
355
|
limit: options?.limit,
|
|
341
356
|
offset: options?.offset,
|
|
342
357
|
},
|
|
358
|
+
signal: options?.signal,
|
|
343
359
|
},
|
|
344
360
|
);
|
|
345
361
|
}
|
|
@@ -350,7 +366,7 @@ export class HindsightApi {
|
|
|
350
366
|
"GET",
|
|
351
367
|
`/v1/default/banks/${encodeURIComponent(bankId)}/documents`,
|
|
352
368
|
"listDocuments",
|
|
353
|
-
{ query: { limit: options?.limit, offset: options?.offset } },
|
|
369
|
+
{ query: { limit: options?.limit, offset: options?.offset }, signal: options?.signal },
|
|
354
370
|
);
|
|
355
371
|
}
|
|
356
372
|
|
|
@@ -370,7 +386,7 @@ export class HindsightApi {
|
|
|
370
386
|
"PATCH",
|
|
371
387
|
`/v1/default/banks/${encodeURIComponent(bankId)}/documents/${encodeURIComponent(documentId)}`,
|
|
372
388
|
"updateDocument",
|
|
373
|
-
{ body: { tags: options.tags } },
|
|
389
|
+
{ body: { tags: options.tags }, signal: options.signal },
|
|
374
390
|
);
|
|
375
391
|
}
|
|
376
392
|
|
|
@@ -399,7 +415,7 @@ export class HindsightApi {
|
|
|
399
415
|
"GET",
|
|
400
416
|
`/v1/default/banks/${encodeURIComponent(bankId)}/mental-models`,
|
|
401
417
|
"listMentalModels",
|
|
402
|
-
{ query: { detail: options?.detail ?? "content" } },
|
|
418
|
+
{ query: { detail: options?.detail ?? "content" }, signal: options?.signal },
|
|
403
419
|
);
|
|
404
420
|
}
|
|
405
421
|
|
|
@@ -413,7 +429,7 @@ export class HindsightApi {
|
|
|
413
429
|
"GET",
|
|
414
430
|
`/v1/default/banks/${encodeURIComponent(bankId)}/mental-models/${encodeURIComponent(mentalModelId)}`,
|
|
415
431
|
"getMentalModel",
|
|
416
|
-
{ query: { detail: options?.detail ?? "content" }, allow404: true },
|
|
432
|
+
{ query: { detail: options?.detail ?? "content" }, allow404: true, signal: options?.signal },
|
|
417
433
|
);
|
|
418
434
|
}
|
|
419
435
|
|
|
@@ -441,6 +457,7 @@ export class HindsightApi {
|
|
|
441
457
|
max_tokens: options?.maxTokens,
|
|
442
458
|
trigger: options?.trigger,
|
|
443
459
|
},
|
|
460
|
+
signal: options?.signal,
|
|
444
461
|
},
|
|
445
462
|
);
|
|
446
463
|
}
|
|
@@ -489,7 +506,11 @@ export class HindsightApi {
|
|
|
489
506
|
if (qs) url += `?${qs}`;
|
|
490
507
|
}
|
|
491
508
|
|
|
492
|
-
const init: RequestInit = {
|
|
509
|
+
const init: RequestInit = {
|
|
510
|
+
method,
|
|
511
|
+
headers: this.#headers,
|
|
512
|
+
signal: withTimeoutSignal(HINDSIGHT_REQUEST_TIMEOUT_MS, opts?.signal),
|
|
513
|
+
};
|
|
493
514
|
if (opts?.body !== undefined) {
|
|
494
515
|
init.body = JSON.stringify(pruneUndefined(opts.body));
|
|
495
516
|
}
|
|
@@ -498,11 +519,10 @@ export class HindsightApi {
|
|
|
498
519
|
try {
|
|
499
520
|
response = await fetch(url, init);
|
|
500
521
|
} catch (err) {
|
|
501
|
-
|
|
502
|
-
`${operation} request
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
);
|
|
522
|
+
const message = isTimeoutError(err)
|
|
523
|
+
? `${operation} request timed out after 30s`
|
|
524
|
+
: `${operation} request failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
525
|
+
throw new HindsightError(message, undefined, err);
|
|
506
526
|
}
|
|
507
527
|
|
|
508
528
|
if (opts?.allow404 && response.status === 404) {
|