@giladbarnea/pi-user-agents 0.0.14 → 0.0.16

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/INTERNALS.md CHANGED
@@ -73,7 +73,7 @@ The file itself appears on the child's first assistant message (`_persist` defer
73
73
 
74
74
  The session file does not persist the dispatch options, so every dispatch writes a `pi-user-agents-dispatch` custom entry into the child — the forwarded pi tokens, the plain task, and the isolate flag. At attach the record replays through the same projections dispatch used (`buildChildResourceLoaderOptions`, `resolveToolOptions`), so `--tools`, `--no-extensions`, `--system-prompt` and friends survive the round trip instead of silently widening back to pi defaults. The model and thinking level deliberately come from the file, not the record: reattachment is pi's own resume path — `SessionManager.open` plus `createAgentSessionFromServices` on a non-empty manager takes `createAgentSession`'s has-existing-session branch — and the file reflects any later changes. A record-less child (dispatched before the record existed, or `/fork`-made) still attaches, with pi's default tools and resources.
75
75
 
76
- The dispatch preamble — built at the dispatch call site, sent verbatim by `runChildTurns`, or its pane counterpart for `-h` children — doubles as the boundary marker: `splitAtDispatchBoundary` finds the first user message carrying either and recovers the child's own conversation for the overlay and the inherited base whose fingerprint re-arms the rebase fast-forward. The fingerprint compares content, not file bookkeeping: rebuilding custom, compaction, and branch summaries from a session file re-stamps their timestamps (and a branch summary's `fromId`), so `conversationFingerprint` drops those fields or a round-tripped base would never equal main's live context. Without a recognizable boundary (a fork child, or a child whose compaction summarized it away) the whole context attaches and the base is a sentinel that never matches, so `r` stays withheld.
76
+ The dispatch preamble — built at the dispatch call site, sent verbatim by `runChildTurns`, or its pane counterpart for `-h` children — doubles as the boundary marker: `splitAtDispatchBoundary` finds the first user message carrying either and recovers the child's own conversation for the overlay and the inherited base whose fingerprint re-arms the rebase fast-forward. The system messages directly before that prompt belong to the child's conversation: Pi persists the child's prompt and tool changes there on its first request, and counting them as base made every reattached child look drifted. The fingerprint compares content, not file bookkeeping: rebuilding custom, compaction, and branch summaries from a session file re-stamps their timestamps (and a branch summary's `fromId`), so `conversationFingerprint` drops those fields or a round-tripped base would never equal main's live context. Without a recognizable boundary (a fork child, or a child whose compaction summarized it away) the whole context attaches and the base is a sentinel that never matches, so `r` stays withheld.
77
77
 
78
78
  The attached agent parks idle first (`runAttachedTurns` waits for an instruction before running the shared turn loop), and its latest persisted response seeds `pendingSquashMessage`, so steering, `s`, and `r` work immediately without a new turn. A successful attach leaves an `Attached session …` breadcrumb — the detach line's counterpart — and both success states end with a "View agent?" confirmation that opens the overlay.
79
79
 
package/attach.ts CHANGED
@@ -53,7 +53,8 @@ export type DispatchBoundary = {
53
53
  /**
54
54
  * Split a child's resolved context at its dispatch boundary — the first user message carrying
55
55
  * a dispatch preamble. Undefined when no message carries one (a /fork child, or a child whose
56
- * own compaction summarized the boundary away).
56
+ * own compaction summarized the boundary away). The system messages right before that prompt are
57
+ * the child's own: Pi persists its prompt and tool changes there on the child's first request.
57
58
  */
58
59
  export function splitAtDispatchBoundary(
59
60
  messages: readonly AgentMessage[],
@@ -63,9 +64,11 @@ export function splitAtDispatchBoundary(
63
64
  const text = userMessageText(message);
64
65
  const preamble = DISPATCH_PREAMBLES.find((candidate) => text.startsWith(candidate));
65
66
  if (preamble === undefined) continue;
67
+ let conversationStart = index;
68
+ while (messages[conversationStart - 1]?.role === "system") conversationStart -= 1;
66
69
  return {
67
- base: messages.slice(0, index),
68
- conversation: messages.slice(index),
70
+ base: messages.slice(0, conversationStart),
71
+ conversation: messages.slice(conversationStart),
69
72
  task: text.slice(preamble.length),
70
73
  };
71
74
  }
package/autocomplete.ts CHANGED
@@ -29,6 +29,7 @@ type CompletionTool = { name: string; description: string };
29
29
  type CompletionResourceCommand = ReturnType<ExtensionAPI["getCommands"]>[number];
30
30
  type ModelScopeSource = Pick<ExtensionContext["modelRegistry"], "getAvailable">;
31
31
  type AutocompleteRuntimeEditor = EditorComponent & {
32
+ cancelAutocomplete?: () => void;
32
33
  forceFileAutocomplete?: (explicitTab?: boolean) => void;
33
34
  getCursor?: () => { line: number; col: number };
34
35
  isShowingAutocomplete?: () => boolean;
@@ -449,15 +450,19 @@ function decorateAutocompleteEditor<T extends EditorComponent>(
449
450
  ): T {
450
451
  const runtimeEditor = editor as DecoratedEditor;
451
452
  if (runtimeEditor[DECORATED_EDITOR]) return editor;
453
+ const cancelAutocomplete = runtimeEditor.cancelAutocomplete;
454
+ if (typeof cancelAutocomplete !== "function")
455
+ throw new Error("pi-user-agents autocomplete requires editor.cancelAutocomplete()");
452
456
  const handleInput = editor.handleInput.bind(editor);
453
457
  editor.handleInput = (data) => {
454
458
  const getCursor = runtimeEditor.getCursor;
455
459
  if (typeof getCursor !== "function")
456
460
  throw new Error("pi-user-agents autocomplete requires editor.getCursor()");
457
461
  const beforeCursor = getCursor.call(editor);
462
+ const beforeText = editor.getText();
458
463
  const beforeExpectation =
459
464
  beforeCursor.line === 0
460
- ? analyzeAgentEditorInput(editor.getText(), beforeCursor.col)
465
+ ? analyzeAgentEditorInput(beforeText, beforeCursor.col)
461
466
  : undefined;
462
467
  const wasShowingAutocomplete = runtimeEditor.isShowingAutocomplete?.call(editor) ?? false;
463
468
  const printableCharacter =
@@ -466,8 +471,12 @@ function decorateAutocompleteEditor<T extends EditorComponent>(
466
471
  handleInput(data);
467
472
 
468
473
  const cursor = getCursor.call(editor);
474
+ const expectation =
475
+ cursor.line === 0 ? analyzeAgentEditorInput(editor.getText(), cursor.col) : undefined;
476
+ // Pi's word and line jumps do not refresh an open menu. Dismiss it before the next key.
477
+ if (wasShowingAutocomplete && beforeExpectation && !expectation && editor.getText() === beforeText)
478
+ cancelAutocomplete.call(editor);
469
479
  if (cursor.line !== 0) return;
470
- const expectation = analyzeAgentEditorInput(editor.getText(), cursor.col);
471
480
  const typedOptionDash = printableCharacter === "-" && expectation?.kind === "option";
472
481
  const completionAcceptance = isCompletionAcceptance(data);
473
482
  const typedEagerValueBoundary =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giladbarnea/pi-user-agents",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "description": "Run, view, and control background Pi agents.",
5
5
  "keywords": [
6
6
  "pi-package",