@awebai/oats-pi 0.35.3 → 0.35.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,14 @@ from the same release tag). This bridge registers no operational tools. It only:
10
10
  acquisition funnel);
11
11
  - contributes the instance-local `.agents/skills` set inside a spawned
12
12
  instance;
13
+ - holds an instance's opening task (`@TASK.md`) on pi's own path until no
14
+ turn another extension started is active, so pi runs it exactly once,
15
+ as its input processing made it, instead of refusing it. It never sends,
16
+ re-sends or alters a message. The exception: an extension loaded behind
17
+ the bridge that starts a turn (or awaits I/O while one starts) inside its
18
+ own input or before_agent_start handling of the opening prompt can still
19
+ make pi refuse it, because pi's interactive mode sends the initial
20
+ message with no streamingBehavior. @awebai/pi has no such handler;
13
21
  - journals compaction summaries and sends resume nudges when the active
14
22
  knowledge capability created `STATE.md`/`log.md`. Knowledge ownership,
15
23
  read/capture instructions, judgment and delivery remain capability-owned.
@@ -6,7 +6,7 @@
6
6
  * pre-workspace oats-getting-started bootstrap outside one, and drives the
7
7
  * memory session events. Ambient skills coexist with the OATS-composed set.
8
8
  */
9
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
10
10
  import { appendLogEntry, PACKAGED_SKILLS_DIR } from "./core-loader.mjs";
11
11
  import { existsSync, readFileSync } from "node:fs";
12
12
  import { join } from "node:path";
@@ -25,6 +25,8 @@ export default function (pi: ExtensionAPI) {
25
25
  });
26
26
 
27
27
  if (isInstance) {
28
+ holdOpeningTask(pi);
29
+
28
30
  pi.on("session_compact", async (event) => {
29
31
  if (!existsSync(join(agentHome!, "STATE.md"))) return;
30
32
  try {
@@ -57,3 +59,58 @@ export default function (pi: ExtensionAPI) {
57
59
  });
58
60
  }
59
61
  }
62
+
63
+ /**
64
+ * The opening task runs once, after any turn another extension starts, as
65
+ * pi's input processing made it. pi sends the launch's `@TASK.md` as the
66
+ * session's first prompt with no streamingBehavior (pi 0.85.1
67
+ * modes/interactive/interactive-mode.js:816) and refuses it while another
68
+ * extension's run (the @awebai/pi welcome) is active. The bridge never sends
69
+ * or alters a message: it holds that prompt on pi's own path, in its input
70
+ * handler and again in before_agent_start (a run can start during the
71
+ * prompt's preflight), until no run is active. A run that an extension behind
72
+ * the bridge starts (or that starts while one awaits) in its own input or
73
+ * before_agent_start handling of the prompt can still make pi refuse it, and
74
+ * pi says nothing an extension could act on. Every later prompt is left to pi.
75
+ */
76
+ function holdOpeningTask(pi: ExtensionAPI) {
77
+ let taken = false;
78
+ // The opening prompt has yet to reach before_agent_start.
79
+ let awaitingStart = false;
80
+ const waiters: (() => void)[] = [];
81
+ // pi refuses a prompt while pi-agent-core holds a run (ctx.signal is that
82
+ // run's), and one with no streamingBehavior while the session is not idle.
83
+ const busy = (ctx: ExtensionContext) => ctx.signal !== undefined || !ctx.isIdle();
84
+ const untilFree = async (ctx: ExtensionContext) => {
85
+ while (busy(ctx)) await new Promise<void>((resolve) => waiters.push(resolve));
86
+ };
87
+ const wake = () => { for (const resume of waiters.splice(0)) resume(); };
88
+ // Waiters re-check on agent_settled (pi 0.80.4 and later). A pi without it
89
+ // releases a run only once every agent_end listener has finished, which no
90
+ // event marks: from agent_end, waiters re-check every 25 ms until none is
91
+ // left waiting or another run starts.
92
+ let recheck: ReturnType<typeof setTimeout> | undefined;
93
+ const stopRechecking = () => { clearTimeout(recheck); recheck = undefined; };
94
+ const recheckWaiters = () => {
95
+ recheck = undefined;
96
+ if (!waiters.length) return;
97
+ wake();
98
+ recheck = setTimeout(recheckWaiters, 25);
99
+ };
100
+
101
+ pi.on("input", async (event, ctx) => {
102
+ if (taken || event.source !== "interactive" || event.streamingBehavior !== undefined) return { action: "continue" };
103
+ taken = true;
104
+ awaitingStart = true;
105
+ await untilFree(ctx);
106
+ return { action: "continue" };
107
+ });
108
+ pi.on("before_agent_start", async (_event, ctx) => {
109
+ if (!awaitingStart) return;
110
+ awaitingStart = false;
111
+ await untilFree(ctx);
112
+ });
113
+ pi.on("agent_start", stopRechecking);
114
+ pi.on("agent_end", () => { stopRechecking(); recheck = setTimeout(recheckWaiters, 0); });
115
+ pi.on("agent_settled", () => { stopRechecking(); wake(); });
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awebai/oats-pi",
3
- "version": "0.35.3",
3
+ "version": "0.35.5",
4
4
  "description": "OATS pi runtime bridge — memory session events and pre-workspace bootstrap over the runtime-neutral @awebai/oats kernel",
5
5
  "keywords": [
6
6
  "pi-package",