shariq-pi-extensions 0.2.4 → 0.2.6
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/docs/EXTENSIONS.md +3 -3
- package/extensions/antigravity-provider/README.md +4 -5
- package/extensions/antigravity-provider/antigravity/accounts.ts +88 -9
- package/extensions/antigravity-provider/antigravity/dashboard.ts +22 -8
- package/extensions/antigravity-provider/antigravity/models.ts +0 -66
- package/extensions/antigravity-provider/antigravity/oauth.ts +21 -1
- package/extensions/antigravity-provider/antigravity/quotas.ts +3 -1
- package/extensions/background-terminals/README.md +2 -2
- package/extensions/background-terminals/index.ts +24 -29
- package/extensions/shared/README.md +1 -0
- package/extensions/shared/settlement-delivery.ts +11 -0
- package/extensions/subagents/README.md +1 -2
- package/extensions/subagents/index.ts +20 -45
- package/extensions/subagents/src/prompt.ts +2 -1
- package/package.json +1 -1
- package/skills/background-terminals/SKILL.md +3 -1
- package/skills/subagents/SKILL.md +2 -2
- package/extensions/subagents/src/result-delivery.ts +0 -20
package/docs/EXTENSIONS.md
CHANGED
|
@@ -6,7 +6,7 @@ Last verified: 2026-08-20
|
|
|
6
6
|
|
|
7
7
|
### [Antigravity Provider](../extensions/antigravity-provider/README.md)
|
|
8
8
|
|
|
9
|
-
Registers the `antigravity` provider and `/login antigravity` flow for
|
|
9
|
+
Registers the `antigravity` provider and `/login antigravity` flow for the latest Gemini Flash, Gemini Pro, Claude Sonnet, and Claude Opus families. Repeating login adds or updates accounts in the secure `<agent-dir>/antigravity/accounts.json` pool. Requests use quota-aware least-recently-used balancing, rotate before streaming on account-specific auth/rate/quota/capacity failures, honor known reset times, and refresh stored OAuth tokens. `/antigravity` shows account state plus the shared five-hour and weekly limits for the Gemini and Claude pools and can enable or disable accounts; `/antigravity.doctor` reports sanitized provider and rotation diagnostics.
|
|
10
10
|
|
|
11
11
|
### [Cursor provider](../extensions/cursor-provider/README.md)
|
|
12
12
|
|
|
@@ -38,7 +38,7 @@ The goal extension adds persistent, branch-safe objectives, progress evidence, b
|
|
|
38
38
|
|
|
39
39
|
The subagent extension runs flat Pi child agents with profiles, capability policies, continuation, result delivery, optional worktrees, and a dashboard. Configuration lives in `<agent-dir>/subagents.json`; trusted projects may override it through their Pi config directory. The configured concurrency ceiling is 50.
|
|
40
40
|
|
|
41
|
-
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement
|
|
41
|
+
The extension supplies tools including `spawn_agent`, `task`, `check_agent`, `list_agents`, `wait_agent`, `send_message`, `close_agent`, `reply_question`, and `apply_agent_changes`. Child settlement is handed to Pi immediately as a turn-triggering follow-up, so Pi queues it while the parent is active or starts a new parent turn when idle; status tools are for explicit inspection, not waiting.
|
|
42
42
|
|
|
43
43
|
### [Orchestration](../extensions/orchestration/README.md)
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ The model-facing `create_orchestration` tool starts planning only after an expli
|
|
|
50
50
|
|
|
51
51
|
Managed PTYs support servers, watchers, long builds, downloads, and interactive processes. The extension tracks up to eight concurrent terminals, retains bounded output, stores full logs in restrictive temporary directories, and stops process groups during shutdown or reload.
|
|
52
52
|
|
|
53
|
-
Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal
|
|
53
|
+
Its tools are `start_terminal`, `read_terminal`, `write_terminal`, `list_terminals`, and `stop_terminal`. A model-started terminal immediately hands its completion or failure to Pi as a turn-triggering follow-up, which Pi queues while the parent is active or uses to start a new parent turn when idle. Reading a terminal does not suppress that delivery; agents should inspect only for explicit progress requests or immediate interaction.
|
|
54
54
|
|
|
55
55
|
## Web access
|
|
56
56
|
|
|
@@ -8,19 +8,18 @@ Local persistent Pi provider for Google Antigravity-compatible models.
|
|
|
8
8
|
- Account and quota dashboard: `/antigravity`
|
|
9
9
|
- Doctor command: `/antigravity.doctor`
|
|
10
10
|
|
|
11
|
-
The provider includes an IPv4 OAuth token-exchange fallback for Node environments where the default request fails. Its
|
|
11
|
+
The provider includes an IPv4 OAuth token-exchange fallback for Node environments where the default request fails. Its curated catalog follows current Antigravity model identifiers and runtime behavior.
|
|
12
12
|
|
|
13
13
|
Successful logins are added to `<agent-dir>/antigravity/accounts.json`, written with owner-only permissions. Existing Pi OAuth credentials are migrated into that pool without exposing token values. Requests select the least recently used eligible account, skip disabled/cooling/exhausted accounts, refresh expiring OAuth tokens, and rotate to another account when an auth, rate, quota, or capacity failure occurs before response streaming begins. Cached per-model remaining quota and reset times guide selection; `/antigravity` refreshes the authoritative catalog and can reversibly enable or disable accounts.
|
|
14
14
|
|
|
15
15
|
Current public model IDs:
|
|
16
16
|
- `antigravity/gemini-3.7-flash`
|
|
17
|
-
- `antigravity/gemini-3.6-flash`
|
|
18
|
-
- `antigravity/gemini-3.5-flash`
|
|
19
17
|
- `antigravity/gemini-3.1-pro`
|
|
20
18
|
- `antigravity/claude-sonnet-4-6`
|
|
21
19
|
- `antigravity/claude-opus-4-6`
|
|
22
|
-
- `antigravity/gpt-oss-120b`
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
The provider intentionally exposes only the latest Gemini Flash and Pro generations and the latest Claude Sonnet and Opus family. `/antigravity` shows the shared five-hour and weekly limits for the Gemini and Claude pools instead of listing every upstream runtime variant.
|
|
22
|
+
|
|
23
|
+
Pi's thinking selector is normalized to Antigravity's runtime IDs: Gemini 3.7 Flash clamps `off`/`minimal` to `low` and `xhigh`/`max` to `high`; Gemini 3.1 Pro clamps to its `low`/`high` pair; Claude models use their fixed-thinking runtime regardless of the selected Pi level.
|
|
25
24
|
|
|
26
25
|
The provider id intentionally remains `antigravity`; existing `~/.pi/agent/auth.json` credentials continue to work without moving secret values. After updating `agy`, compare `agy models` with `antigravity/models.ts` before changing this deterministic catalog.
|
|
@@ -14,11 +14,13 @@ const CAPACITY_COOLDOWN_MS = 60 * 1_000;
|
|
|
14
14
|
const REFRESH_SKEW_MS = 60 * 1_000;
|
|
15
15
|
|
|
16
16
|
export type AntigravityQuotaGroup = "gemini" | "non-gemini";
|
|
17
|
+
export type AntigravityQuotaWindow = "five-hour" | "weekly";
|
|
17
18
|
|
|
18
19
|
export interface AntigravityQuotaEntry {
|
|
19
20
|
modelId: string;
|
|
20
21
|
displayName?: string;
|
|
21
22
|
group: AntigravityQuotaGroup;
|
|
23
|
+
window?: AntigravityQuotaWindow;
|
|
22
24
|
remainingFraction: number;
|
|
23
25
|
resetTime?: string;
|
|
24
26
|
}
|
|
@@ -78,6 +80,7 @@ function normalizeAccount(value: unknown): AntigravityAccount | undefined {
|
|
|
78
80
|
modelId: entry.modelId,
|
|
79
81
|
displayName: typeof entry.displayName === "string" ? entry.displayName : undefined,
|
|
80
82
|
group: entry.group,
|
|
83
|
+
window: entry.window === "five-hour" || entry.window === "weekly" ? entry.window : undefined,
|
|
81
84
|
remainingFraction: Math.max(0, Math.min(1, remainingFraction)),
|
|
82
85
|
resetTime: typeof entry.resetTime === "string" ? entry.resetTime : undefined,
|
|
83
86
|
}];
|
|
@@ -281,7 +284,74 @@ export function updateAntigravityAccountQuota(id: string, quota: AntigravityQuot
|
|
|
281
284
|
}
|
|
282
285
|
|
|
283
286
|
export function parseAntigravityQuota(value: unknown): AntigravityQuotaEntry[] {
|
|
284
|
-
const
|
|
287
|
+
const findGroups = (node: unknown): unknown[] | undefined => {
|
|
288
|
+
if (!node || typeof node !== "object") return undefined;
|
|
289
|
+
if (Array.isArray(node)) {
|
|
290
|
+
for (const item of node) {
|
|
291
|
+
const found = findGroups(item);
|
|
292
|
+
if (found) return found;
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
const raw = node as Record<string, unknown>;
|
|
297
|
+
if (Array.isArray(raw.groups)) return raw.groups;
|
|
298
|
+
for (const child of Object.values(raw)) {
|
|
299
|
+
const found = findGroups(child);
|
|
300
|
+
if (found) return found;
|
|
301
|
+
}
|
|
302
|
+
return undefined;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const summaryEntries: AntigravityQuotaEntry[] = [];
|
|
306
|
+
for (const groupValue of findGroups(value) ?? []) {
|
|
307
|
+
if (!groupValue || typeof groupValue !== "object") continue;
|
|
308
|
+
const group = groupValue as Record<string, unknown>;
|
|
309
|
+
const groupLabel = [group.displayName, group.name, group.id].find((item): item is string => typeof item === "string") ?? "";
|
|
310
|
+
const quotaGroup: AntigravityQuotaGroup | undefined = /gemini/i.test(groupLabel)
|
|
311
|
+
? "gemini"
|
|
312
|
+
: /claude|third.party|3p|gpt/i.test(groupLabel)
|
|
313
|
+
? "non-gemini"
|
|
314
|
+
: undefined;
|
|
315
|
+
if (!quotaGroup || !Array.isArray(group.buckets)) continue;
|
|
316
|
+
for (const bucketValue of group.buckets) {
|
|
317
|
+
if (!bucketValue || typeof bucketValue !== "object") continue;
|
|
318
|
+
const bucket = bucketValue as Record<string, unknown>;
|
|
319
|
+
const remaining = bucket.remaining && typeof bucket.remaining === "object"
|
|
320
|
+
? bucket.remaining as Record<string, unknown>
|
|
321
|
+
: undefined;
|
|
322
|
+
const remainingFraction = finiteNumber(bucket.remainingFraction) ?? finiteNumber(remaining?.remainingFraction);
|
|
323
|
+
const windowLabel = [bucket.window, bucket.bucketId, bucket.displayName].find((item): item is string => typeof item === "string") ?? "";
|
|
324
|
+
const window: AntigravityQuotaWindow | undefined = /week/i.test(windowLabel)
|
|
325
|
+
? "weekly"
|
|
326
|
+
: /5h|five.?hour/i.test(windowLabel)
|
|
327
|
+
? "five-hour"
|
|
328
|
+
: undefined;
|
|
329
|
+
if (remainingFraction === undefined || !window) continue;
|
|
330
|
+
summaryEntries.push({
|
|
331
|
+
modelId: typeof bucket.bucketId === "string" ? bucket.bucketId : `${quotaGroup}-${window}`,
|
|
332
|
+
displayName: window === "weekly" ? "Weekly" : "5-Hour",
|
|
333
|
+
group: quotaGroup,
|
|
334
|
+
window,
|
|
335
|
+
remainingFraction: Math.max(0, Math.min(1, remainingFraction)),
|
|
336
|
+
resetTime: typeof bucket.resetTime === "string"
|
|
337
|
+
? bucket.resetTime
|
|
338
|
+
: typeof remaining?.resetTime === "string"
|
|
339
|
+
? remaining.resetTime
|
|
340
|
+
: undefined,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (summaryEntries.length) return summaryEntries;
|
|
345
|
+
|
|
346
|
+
const supportedRuntimeIds = new Set(
|
|
347
|
+
Object.entries(ANTIGRAVITY_ROUTING).flatMap(([modelId, routing]) => [
|
|
348
|
+
modelId,
|
|
349
|
+
routing.off,
|
|
350
|
+
routing.defaultRequestId,
|
|
351
|
+
...Object.values(routing.routing ?? {}),
|
|
352
|
+
]).filter((item): item is string => typeof item === "string"),
|
|
353
|
+
);
|
|
354
|
+
const modelEntries: AntigravityQuotaEntry[] = [];
|
|
285
355
|
const seen = new Set<string>();
|
|
286
356
|
const visit = (node: unknown, keyHint?: string) => {
|
|
287
357
|
if (!node || typeof node !== "object") return;
|
|
@@ -291,17 +361,17 @@ export function parseAntigravityQuota(value: unknown): AntigravityQuotaEntry[] {
|
|
|
291
361
|
}
|
|
292
362
|
const raw = node as Record<string, unknown>;
|
|
293
363
|
const quota = raw.quotaInfo && typeof raw.quotaInfo === "object" ? raw.quotaInfo as Record<string, unknown> : raw;
|
|
294
|
-
const
|
|
295
|
-
const modelId = [raw.modelId, raw.id, raw.name, raw.model, keyHint].find((item): item is string => typeof item === "string"
|
|
296
|
-
if (modelId &&
|
|
364
|
+
const remainingFraction = finiteNumber(quota.remainingFraction);
|
|
365
|
+
const modelId = [raw.modelId, raw.id, raw.name, raw.model, keyHint].find((item): item is string => typeof item === "string");
|
|
366
|
+
if (modelId && remainingFraction !== undefined) {
|
|
297
367
|
const normalized = modelId.replace(/^models\//, "");
|
|
298
|
-
if (!seen.has(normalized)) {
|
|
368
|
+
if (supportedRuntimeIds.has(normalized) && !seen.has(normalized)) {
|
|
299
369
|
seen.add(normalized);
|
|
300
|
-
|
|
370
|
+
modelEntries.push({
|
|
301
371
|
modelId: normalized,
|
|
302
|
-
displayName: typeof raw.displayName === "string" ? raw.displayName : typeof raw.label === "string" ? raw.label : undefined,
|
|
303
372
|
group: normalized.startsWith("gemini-") ? "gemini" : "non-gemini",
|
|
304
|
-
|
|
373
|
+
window: "five-hour",
|
|
374
|
+
remainingFraction: Math.max(0, Math.min(1, remainingFraction)),
|
|
305
375
|
resetTime: typeof quota.resetTime === "string" ? quota.resetTime : undefined,
|
|
306
376
|
});
|
|
307
377
|
}
|
|
@@ -311,7 +381,16 @@ export function parseAntigravityQuota(value: unknown): AntigravityQuotaEntry[] {
|
|
|
311
381
|
}
|
|
312
382
|
};
|
|
313
383
|
visit(value);
|
|
314
|
-
|
|
384
|
+
|
|
385
|
+
return (["gemini", "non-gemini"] as const).flatMap((group) => {
|
|
386
|
+
const entries = modelEntries.filter((entry) => entry.group === group).sort((left, right) => left.remainingFraction - right.remainingFraction);
|
|
387
|
+
const mostConstrained = entries[0];
|
|
388
|
+
return mostConstrained ? [{
|
|
389
|
+
...mostConstrained,
|
|
390
|
+
modelId: group === "gemini" ? "gemini-5h" : "claude-5h",
|
|
391
|
+
displayName: "5-Hour",
|
|
392
|
+
}] : [];
|
|
393
|
+
});
|
|
315
394
|
}
|
|
316
395
|
|
|
317
396
|
const refreshes = new Map<string, Promise<AntigravityAccount>>();
|
|
@@ -29,7 +29,22 @@ function until(timestamp: number | string | undefined) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function groupQuota(quota: AntigravityQuotaEntry[] | undefined, group: "gemini" | "non-gemini") {
|
|
32
|
-
|
|
32
|
+
const byWindow = new Map<"five-hour" | "weekly", AntigravityQuotaEntry>();
|
|
33
|
+
for (const entry of (quota ?? []).filter((candidate) => candidate.group === group)) {
|
|
34
|
+
const window = entry.window ?? "five-hour";
|
|
35
|
+
const existing = byWindow.get(window);
|
|
36
|
+
if (!existing || entry.remainingFraction < existing.remainingFraction) {
|
|
37
|
+
byWindow.set(window, {
|
|
38
|
+
...entry,
|
|
39
|
+
window,
|
|
40
|
+
displayName: window === "weekly" ? "Weekly" : "5-Hour",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return (["five-hour", "weekly"] as const).flatMap((window) => {
|
|
45
|
+
const entry = byWindow.get(window);
|
|
46
|
+
return entry ? [entry] : [];
|
|
47
|
+
});
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
function accountState(account: AntigravityAccountStatus) {
|
|
@@ -50,8 +65,7 @@ function stateColor(state: string) {
|
|
|
50
65
|
|
|
51
66
|
function compactGroup(label: string, entries: AntigravityQuotaEntry[]) {
|
|
52
67
|
if (!entries.length) return `${label}: unavailable`;
|
|
53
|
-
|
|
54
|
-
return `${label}: ${remaining(worst.remainingFraction)} remaining${worst.resetTime ? ` · resets ${until(worst.resetTime)}` : ""}`;
|
|
68
|
+
return `${label}: ${entries.map((entry) => `${entry.displayName} ${remaining(entry.remainingFraction)}${entry.resetTime ? ` · resets ${until(entry.resetTime)}` : ""}`).join(" · ")}`;
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
export class AntigravityDashboard implements Component {
|
|
@@ -159,7 +173,7 @@ export class AntigravityDashboard implements Component {
|
|
|
159
173
|
const lines = width >= 64
|
|
160
174
|
? [joinSides(title, `${right} `, width)]
|
|
161
175
|
: [truncateToWidth(title, width, ""), truncateToWidth(` ${right}`, width, "")];
|
|
162
|
-
lines.push(frameTop(this.theme, width, `ACCOUNTS ${accounts.length} ·
|
|
176
|
+
lines.push(frameTop(this.theme, width, `ACCOUNTS ${accounts.length} · 5-HOUR + WEEKLY LIMITS REMAINING`));
|
|
163
177
|
const inner = Math.max(1, width - 2);
|
|
164
178
|
if (width >= 104 && selected) {
|
|
165
179
|
const leftWidth = Math.max(46, Math.floor((inner - 1) * 0.45));
|
|
@@ -195,7 +209,7 @@ export class AntigravityDashboard implements Component {
|
|
|
195
209
|
lines.push(selected ? this.theme.bg("selectedBg", padLine(first, width)) : first);
|
|
196
210
|
if (!compact) {
|
|
197
211
|
lines.push(` ${this.theme.fg("muted", compactGroup("Gemini", groupQuota(account.quota, "gemini")))}`);
|
|
198
|
-
lines.push(` ${this.theme.fg("muted", compactGroup("Claude
|
|
212
|
+
lines.push(` ${this.theme.fg("muted", compactGroup("Claude", groupQuota(account.quota, "non-gemini")))}`);
|
|
199
213
|
lines.push("");
|
|
200
214
|
}
|
|
201
215
|
}
|
|
@@ -211,8 +225,8 @@ export class AntigravityDashboard implements Component {
|
|
|
211
225
|
if (account.cooldownUntil && account.cooldownUntil > Date.now()) lines.push(` ${this.theme.fg("warning", `${account.cooldownReason || "rotation"} cooldown · ${until(account.cooldownUntil)}`)}`);
|
|
212
226
|
if (account.quotaError) lines.push(` ${this.theme.fg("error", oneLine(account.quotaError))}`);
|
|
213
227
|
if (account.lastError) lines.push(` ${this.theme.fg("warning", oneLine(account.lastError))}`);
|
|
214
|
-
lines.push("", ...this.renderQuotaGroup("Gemini", groupQuota(account.quota, "gemini"), width));
|
|
215
|
-
lines.push("", ...this.renderQuotaGroup("Claude
|
|
228
|
+
lines.push("", ...this.renderQuotaGroup("Gemini models", groupQuota(account.quota, "gemini"), width));
|
|
229
|
+
lines.push("", ...this.renderQuotaGroup("Claude models", groupQuota(account.quota, "non-gemini"), width));
|
|
216
230
|
if (account.lastUsedAt) lines.push("", ` ${this.theme.fg("muted", "LAST USED")} ${this.theme.fg("text", new Date(account.lastUsedAt).toLocaleString())}`);
|
|
217
231
|
return lines.slice(0, height);
|
|
218
232
|
}
|
|
@@ -221,7 +235,7 @@ export class AntigravityDashboard implements Component {
|
|
|
221
235
|
const lines = [` ${this.theme.fg("accent", this.theme.bold(`${name.toUpperCase()} · REMAINING`))}`];
|
|
222
236
|
if (!entries.length) return [...lines, ` ${this.theme.fg("muted", "Unavailable")}`];
|
|
223
237
|
const meterWidth = Math.max(10, width - 29);
|
|
224
|
-
for (const entry of entries
|
|
238
|
+
for (const entry of entries) {
|
|
225
239
|
const percent = entry.remainingFraction * 100;
|
|
226
240
|
const color = percent <= 0 ? "error" : percent <= 15 ? "warning" : "active";
|
|
227
241
|
const label = oneLine(entry.displayName || entry.modelId).slice(0, 16).padEnd(16);
|
|
@@ -59,42 +59,6 @@ export const ANTIGRAVITY_ROUTING: Record<string, AntigravityRouting> = {
|
|
|
59
59
|
},
|
|
60
60
|
defaultRequestId: "gemini-3.7-flash-low",
|
|
61
61
|
},
|
|
62
|
-
"gemini-3.6-flash": {
|
|
63
|
-
off: "gemini-3.6-flash-low",
|
|
64
|
-
routing: {
|
|
65
|
-
minimal: "gemini-3.6-flash-low",
|
|
66
|
-
low: "gemini-3.6-flash-low",
|
|
67
|
-
medium: "gemini-3.6-flash-medium",
|
|
68
|
-
high: "gemini-3.6-flash-high",
|
|
69
|
-
xhigh: "gemini-3.6-flash-high",
|
|
70
|
-
max: "gemini-3.6-flash-high",
|
|
71
|
-
},
|
|
72
|
-
defaultRequestId: "gemini-3.6-flash-low",
|
|
73
|
-
},
|
|
74
|
-
"gemini-3.5-flash": {
|
|
75
|
-
off: "gemini-3.5-flash-extra-low",
|
|
76
|
-
routing: {
|
|
77
|
-
minimal: "gemini-3.5-flash-extra-low",
|
|
78
|
-
low: "gemini-3.5-flash-extra-low",
|
|
79
|
-
medium: "gemini-3.5-flash-low",
|
|
80
|
-
high: "gemini-3-flash-agent",
|
|
81
|
-
xhigh: "gemini-3-flash-agent",
|
|
82
|
-
max: "gemini-3-flash-agent",
|
|
83
|
-
},
|
|
84
|
-
defaultRequestId: "gemini-3.5-flash-low",
|
|
85
|
-
},
|
|
86
|
-
"gpt-oss-120b": {
|
|
87
|
-
off: "gpt-oss-120b-medium",
|
|
88
|
-
routing: {
|
|
89
|
-
minimal: "gpt-oss-120b-medium",
|
|
90
|
-
low: "gpt-oss-120b-medium",
|
|
91
|
-
medium: "gpt-oss-120b-medium",
|
|
92
|
-
high: "gpt-oss-120b-medium",
|
|
93
|
-
xhigh: "gpt-oss-120b-medium",
|
|
94
|
-
max: "gpt-oss-120b-medium",
|
|
95
|
-
},
|
|
96
|
-
defaultRequestId: "gpt-oss-120b-medium",
|
|
97
|
-
},
|
|
98
62
|
};
|
|
99
63
|
|
|
100
64
|
export const ANTIGRAVITY_MODELS: ProviderModelConfig[] = [
|
|
@@ -138,36 +102,6 @@ export const ANTIGRAVITY_MODELS: ProviderModelConfig[] = [
|
|
|
138
102
|
contextWindow: 1048576,
|
|
139
103
|
maxTokens: 65536,
|
|
140
104
|
},
|
|
141
|
-
{
|
|
142
|
-
id: "gemini-3.6-flash",
|
|
143
|
-
name: "Gemini 3.6 Flash (Antigravity)",
|
|
144
|
-
reasoning: true,
|
|
145
|
-
thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "MEDIUM", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
|
|
146
|
-
input: ["text", "image"],
|
|
147
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
148
|
-
contextWindow: 1048576,
|
|
149
|
-
maxTokens: 65536,
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: "gemini-3.5-flash",
|
|
153
|
-
name: "Gemini 3.5 Flash (Antigravity)",
|
|
154
|
-
reasoning: true,
|
|
155
|
-
thinkingLevelMap: { off: null, minimal: "LOW", low: "LOW", medium: "MEDIUM", high: "HIGH", xhigh: "HIGH", max: "HIGH" } as any,
|
|
156
|
-
input: ["text", "image"],
|
|
157
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
158
|
-
contextWindow: 1048576,
|
|
159
|
-
maxTokens: 65536,
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
id: "gpt-oss-120b",
|
|
163
|
-
name: "GPT-OSS 120B (Antigravity)",
|
|
164
|
-
reasoning: true,
|
|
165
|
-
thinkingLevelMap: { off: null, minimal: "MEDIUM", low: "MEDIUM", medium: "MEDIUM", high: "MEDIUM", xhigh: "MEDIUM", max: "MEDIUM" } as any,
|
|
166
|
-
input: ["text"],
|
|
167
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
168
|
-
contextWindow: 131072,
|
|
169
|
-
maxTokens: 32768,
|
|
170
|
-
},
|
|
171
105
|
];
|
|
172
106
|
|
|
173
107
|
export function getAntigravityRequestModelId(modelId: string, effort: string | undefined): string {
|
|
@@ -322,8 +322,28 @@ function findDynamicModel(value: any, requestedId: string): DynamicModelInfo | u
|
|
|
322
322
|
|
|
323
323
|
export async function fetchAntigravityQuotaCatalog(token: string, projectId: string, signal?: AbortSignal): Promise<unknown> {
|
|
324
324
|
let lastFailure = "Antigravity quota catalog is unavailable.";
|
|
325
|
-
const bodies = [{}, { cloudaicompanionProject: projectId }, { project: projectId }];
|
|
326
325
|
for (const endpoint of endpointCandidates()) {
|
|
326
|
+
try {
|
|
327
|
+
const res = await fetch(`${endpoint}/v1internal:retrieveUserQuotaSummary`, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: antigravityHeaders(token),
|
|
330
|
+
body: "{}",
|
|
331
|
+
signal: boundedFetchSignal(signal),
|
|
332
|
+
});
|
|
333
|
+
lastStatus = res.status;
|
|
334
|
+
lastEndpoint = endpoint;
|
|
335
|
+
if (res.ok) {
|
|
336
|
+
const summary = await res.json() as { groups?: unknown[] };
|
|
337
|
+
if (Array.isArray(summary.groups) && summary.groups.length) return summary;
|
|
338
|
+
}
|
|
339
|
+
else lastFailure = `HTTP ${res.status}: ${jsonOrTextError(await res.text())}`;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (signal?.aborted) throw error;
|
|
342
|
+
lastFailure = safeError(error);
|
|
343
|
+
lastError = lastFailure;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const bodies = [{}, { cloudaicompanionProject: projectId }, { project: projectId }];
|
|
327
347
|
for (const candidateBody of bodies) {
|
|
328
348
|
try {
|
|
329
349
|
const res = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
|
|
@@ -20,7 +20,9 @@ export function refreshAntigravityQuotas(options: { force?: boolean; signal?: Ab
|
|
|
20
20
|
const accounts = antigravityAccountStatuses(now);
|
|
21
21
|
for (const account of accounts) {
|
|
22
22
|
if (account.disabled) continue;
|
|
23
|
-
|
|
23
|
+
const hasBothWindows = account.quota?.some((entry) => entry.window === "five-hour") &&
|
|
24
|
+
account.quota.some((entry) => entry.window === "weekly");
|
|
25
|
+
if (!options.force && hasBothWindows && account.quotaUpdatedAt && now - account.quotaUpdatedAt < ANTIGRAVITY_QUOTA_TTL_MS) continue;
|
|
24
26
|
try {
|
|
25
27
|
const resolved = await resolveAntigravityAccount(account, refreshAntigravityToken, now);
|
|
26
28
|
const catalog = await fetchAntigravityQuotaCatalog(resolved.access, resolved.projectId, options.signal);
|
|
@@ -10,7 +10,7 @@ Session-scoped background pseudo-terminals for Pi. The extension combines Codex-
|
|
|
10
10
|
- `list_terminals` — list running and settled terminals.
|
|
11
11
|
- `stop_terminal` — stop complete process groups with TERM-to-KILL escalation.
|
|
12
12
|
|
|
13
|
-
Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout.
|
|
13
|
+
Each output response carries a byte cursor. Pass it to the next read/write operation to avoid repeating output. Long or uncertain commands should use `start_terminal` instead of a large blocking `bash` timeout. Settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts a new parent turn when idle, so the parent can continue other work or end its turn rather than poll.
|
|
14
14
|
|
|
15
15
|
## User interface
|
|
16
16
|
|
|
@@ -34,7 +34,7 @@ Each output response carries a byte cursor. Pass it to the next read/write opera
|
|
|
34
34
|
- Output is sanitized before TUI or model rendering.
|
|
35
35
|
- Processes run in their own PTY process group and are stopped on session shutdown, replacement, or reload.
|
|
36
36
|
- Shutdown and stop operations are bounded and escalate from SIGTERM to SIGKILL.
|
|
37
|
-
- Model-started terminals
|
|
37
|
+
- Model-started terminals immediately hand one completion/failure follow-up to Pi; Pi queues it while the parent is active or starts the next parent turn when idle.
|
|
38
38
|
- Reading settled output does not consume or suppress the automatic completion delivery.
|
|
39
39
|
- Completion delivery is keyed by terminal id to prevent duplicate follow-ups.
|
|
40
40
|
|
|
@@ -3,12 +3,12 @@ import * as path from "node:path";
|
|
|
3
3
|
import type {
|
|
4
4
|
ExtensionAPI,
|
|
5
5
|
ExtensionCommandContext,
|
|
6
|
-
ExtensionContext,
|
|
7
6
|
ExtensionUIContext,
|
|
8
7
|
} from "@earendil-works/pi-coding-agent";
|
|
9
8
|
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
10
9
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
11
10
|
import { Type } from "typebox";
|
|
11
|
+
import { deliverSettlement } from "../shared/settlement-delivery.ts";
|
|
12
12
|
import { oneLine, sanitizeTerminalText, stateLabel } from "../shared/tui-dashboard.ts";
|
|
13
13
|
import { TerminalManager, MAX_RUNNING_TERMINALS } from "./src/manager.ts";
|
|
14
14
|
import {
|
|
@@ -37,12 +37,12 @@ function resolveCwd(base: string, requested?: string): string {
|
|
|
37
37
|
|
|
38
38
|
export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
39
39
|
let manager: TerminalManager | undefined;
|
|
40
|
-
let sessionContext: ExtensionContext | undefined;
|
|
41
40
|
let ui: ExtensionUIContext | undefined;
|
|
42
41
|
let unsubscribe: (() => void) | undefined;
|
|
43
42
|
let lastStatus = "";
|
|
44
43
|
const pendingResults = new Map<string, TerminalSnapshot>();
|
|
45
44
|
const modelOwned = new Set<string>();
|
|
45
|
+
const starting = new Set<string>();
|
|
46
46
|
|
|
47
47
|
const getManager = (): TerminalManager => {
|
|
48
48
|
if (manager) return manager;
|
|
@@ -56,7 +56,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
pendingResults.set(snapshot.id, snapshot);
|
|
59
|
-
if (
|
|
59
|
+
if (!starting.has(snapshot.id)) flushResult(snapshot.id);
|
|
60
60
|
});
|
|
61
61
|
unsubscribe = manager.view.subscribe(updateStatus);
|
|
62
62
|
updateStatus();
|
|
@@ -85,40 +85,31 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
function flushResult(id: string): void {
|
|
89
|
+
const snapshot = pendingResults.get(id);
|
|
90
|
+
if (!snapshot) return;
|
|
91
|
+
pendingResults.delete(id);
|
|
92
|
+
deliverSettlement(pi, {
|
|
93
|
+
customType: "background-terminal-result",
|
|
94
|
+
content: formatCompletion(snapshot),
|
|
95
|
+
display: true,
|
|
96
|
+
details: {
|
|
97
|
+
id: snapshot.id,
|
|
98
|
+
title: snapshot.title,
|
|
99
|
+
status: snapshot.status,
|
|
100
|
+
exitCode: snapshot.exitCode,
|
|
100
101
|
},
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function flushResults(): void {
|
|
106
|
-
const results = [...pendingResults.values()];
|
|
107
|
-
pendingResults.clear();
|
|
108
|
-
for (const snapshot of results) deliver(snapshot);
|
|
102
|
+
});
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
pi.on("session_start", (_event, ctx) => {
|
|
112
|
-
sessionContext = ctx;
|
|
113
106
|
if (ctx.hasUI) ui = ctx.ui;
|
|
114
107
|
});
|
|
115
108
|
|
|
116
|
-
pi.on("agent_settled", flushResults);
|
|
117
|
-
|
|
118
109
|
pi.on("session_shutdown", async () => {
|
|
119
|
-
sessionContext = undefined;
|
|
120
110
|
pendingResults.clear();
|
|
121
111
|
modelOwned.clear();
|
|
112
|
+
starting.clear();
|
|
122
113
|
unsubscribe?.();
|
|
123
114
|
unsubscribe = undefined;
|
|
124
115
|
ui?.setStatus(STATUS_KEY, undefined);
|
|
@@ -140,7 +131,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
140
131
|
promptSnippet: "Start an interactive or long-running command in a managed background PTY.",
|
|
141
132
|
promptGuidelines: [
|
|
142
133
|
"Use start_terminal by default for servers, watchers, downloads, long or uncertain builds and tests, interactive shells, and any command that should not occupy the main turn; reserve bash for short commands whose result is needed immediately. Never use a large bash timeout merely to wait for long work.",
|
|
143
|
-
"After start_terminal returns, continue only genuinely independent work. If none remains, end the turn immediately.
|
|
134
|
+
"After start_terminal returns, continue only genuinely independent work. If none remains, end the turn immediately. Terminal settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts the next parent turn when idle. When that result invokes the parent, continue the original task immediately without waiting for the user or rereading the same terminal; do not call read_terminal, list_terminals, or start a timer merely to check whether it finished.",
|
|
144
135
|
"Use stop_terminal when a managed process is no longer needed. Background terminals are session-scoped and are stopped during session shutdown or reload.",
|
|
145
136
|
],
|
|
146
137
|
parameters: Type.Object({
|
|
@@ -158,6 +149,7 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
158
149
|
cwd: resolveCwd(ctx.cwd, params.working_dir),
|
|
159
150
|
});
|
|
160
151
|
modelOwned.add(terminal.id);
|
|
152
|
+
starting.add(terminal.id);
|
|
161
153
|
let result: TerminalReadResult;
|
|
162
154
|
try {
|
|
163
155
|
result = await getManager().read(terminal.id, {
|
|
@@ -166,12 +158,15 @@ export default function backgroundTerminals(pi: ExtensionAPI) {
|
|
|
166
158
|
signal,
|
|
167
159
|
});
|
|
168
160
|
} catch (error) {
|
|
169
|
-
|
|
161
|
+
starting.delete(terminal.id);
|
|
170
162
|
modelOwned.delete(terminal.id);
|
|
163
|
+
await getManager().kill([terminal.id]).catch(() => undefined);
|
|
171
164
|
consume(terminal.id);
|
|
172
165
|
throw error;
|
|
173
166
|
}
|
|
167
|
+
starting.delete(terminal.id);
|
|
174
168
|
if (result.snapshot.status !== "running") consume(terminal.id);
|
|
169
|
+
else flushResult(terminal.id);
|
|
175
170
|
let text = `Started background terminal ${terminal.id} "${oneLine(terminal.title)}" (pid ${terminal.pid}). Use cursor ${result.cursor} for incremental reads.`;
|
|
176
171
|
if (result.text || result.snapshot.status !== "running") {
|
|
177
172
|
text += `\n\n${formatReadResult(result)}`;
|
|
@@ -8,6 +8,7 @@ Internal runtime utilities used by more than one extension. This directory is no
|
|
|
8
8
|
- `child-session.ts` owns trust-aware child resources and bounded session shutdown.
|
|
9
9
|
- `context-utilization.ts` formats model-context usage and capacity.
|
|
10
10
|
- `dashboard-state.ts` keeps list selection stable as live rows change.
|
|
11
|
+
- `settlement-delivery.ts` immediately hands asynchronous results to Pi as turn-triggering follow-ups.
|
|
11
12
|
- `tool-call-timeout.ts` applies cancellation-aware execution limits to registered tools.
|
|
12
13
|
- `tui-dashboard.ts` provides bounded, sanitized terminal-dashboard rendering helpers.
|
|
13
14
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type SettlementMessage = Parameters<ExtensionAPI["sendMessage"]>[0];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hand settlement to Pi immediately. Pi starts a turn when idle and queues the
|
|
7
|
+
* same message as a follow-up when the parent is still running.
|
|
8
|
+
*/
|
|
9
|
+
export function deliverSettlement(pi: ExtensionAPI, message: SettlementMessage): void {
|
|
10
|
+
pi.sendMessage(message, { deliverAs: "followUp", triggerTurn: true });
|
|
11
|
+
}
|
|
@@ -19,7 +19,7 @@ The system is deliberately flat. Only the main Pi thread can spawn subagents. Ch
|
|
|
19
19
|
- `reply_question` — answer a child’s blocking `ask_parent` request
|
|
20
20
|
- `task` — atomically reserve capacity for up to the configured limit (maximum 50), start the fan-out in the background, and return child ids immediately
|
|
21
21
|
|
|
22
|
-
Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents.
|
|
22
|
+
Child sessions receive `message_parent`, `ask_parent`, `list_peers`, and `message_peer`. Peer messages are routed through the main-thread manager and can steer a running child or continue a settled one; they cannot create agents. Child settlement is handed to Pi immediately: it queues a follow-up while the parent is active or starts a new parent turn when idle, so the main turn can continue independent work or end and remain available to the user.
|
|
23
23
|
|
|
24
24
|
## Profiles and capabilities
|
|
25
25
|
|
|
@@ -86,4 +86,3 @@ Children share the requested workspace by default. Use `isolation: "worktree"` o
|
|
|
86
86
|
- `src/worktree.ts` — isolated worktree creation, inspection, conflict preflight, integration, and cleanup
|
|
87
87
|
- `src/runtime.ts` — managed runtime and Pi backend registry
|
|
88
88
|
- `src/ui/` — dashboard, transcript, and takeover components
|
|
89
|
-
- `src/result-delivery.ts` — deferred automatic result delivery
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "@earendil-works/pi-coding-agent";
|
|
27
27
|
import { Markdown, Text } from "@earendil-works/pi-tui";
|
|
28
28
|
import { Type } from "typebox";
|
|
29
|
+
import { deliverSettlement } from "../shared/settlement-delivery.ts";
|
|
29
30
|
import {
|
|
30
31
|
formatElapsed,
|
|
31
32
|
latestText,
|
|
@@ -56,7 +57,6 @@ import {
|
|
|
56
57
|
SUBAGENT_WAIT_TOOL_DESCRIPTION,
|
|
57
58
|
WORKTREE_ISOLATION_DESCRIPTION,
|
|
58
59
|
} from "./src/prompt.ts";
|
|
59
|
-
import { createDeferredResultDelivery } from "./src/result-delivery.ts";
|
|
60
60
|
import {
|
|
61
61
|
CAPABILITY_MODES,
|
|
62
62
|
ISOLATION_MODES,
|
|
@@ -197,10 +197,8 @@ function resolveChildProjectTrust(options: {
|
|
|
197
197
|
export default function (pi: ExtensionAPI) {
|
|
198
198
|
let runtime: SubagentRuntime | undefined;
|
|
199
199
|
let managerPromise: Promise<SubagentManagerShape> | undefined;
|
|
200
|
-
let sessionContext: ExtensionContext | undefined;
|
|
201
200
|
let ui: ExtensionUIContext | undefined;
|
|
202
201
|
let unsubStatus: (() => void) | undefined;
|
|
203
|
-
const resultDelivery = createDeferredResultDelivery<SubagentSnapshot>();
|
|
204
202
|
const archived = new Map<string, ArchivedSubagent>();
|
|
205
203
|
const peerHistory: PeerMessage[] = [];
|
|
206
204
|
const pendingQuestions = new Map<
|
|
@@ -524,35 +522,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
524
522
|
};
|
|
525
523
|
|
|
526
524
|
const deliverResult = (snap: SubagentSnapshot) => {
|
|
527
|
-
pi
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
},
|
|
540
|
-
{ deliverAs: "followUp", triggerTurn: true },
|
|
541
|
-
);
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
const flushResults = () => {
|
|
545
|
-
for (const snap of resultDelivery.drain()) deliverResult(snap);
|
|
525
|
+
deliverSettlement(pi, {
|
|
526
|
+
customType: "subagent-result",
|
|
527
|
+
content: buildSubagentResultMessage({
|
|
528
|
+
id: snap.id,
|
|
529
|
+
title: snap.title,
|
|
530
|
+
status: snap.status,
|
|
531
|
+
errorText: snap.errorText,
|
|
532
|
+
output: truncatedOutput(snap),
|
|
533
|
+
}),
|
|
534
|
+
display: true,
|
|
535
|
+
details: { id: snap.id, title: snap.title, status: snap.status },
|
|
536
|
+
});
|
|
546
537
|
};
|
|
547
538
|
|
|
548
539
|
const onSettled = (snap: SubagentSnapshot, consumed: boolean) => {
|
|
549
540
|
persistSnapshot(snap);
|
|
550
|
-
if (snap.meta.origin === "orchestration")
|
|
551
|
-
resultDelivery.consume([snap.id]);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
541
|
+
if (snap.meta.origin === "orchestration") return;
|
|
554
542
|
if (snap.meta.origin === "btw") {
|
|
555
|
-
resultDelivery.consume([snap.id]);
|
|
556
543
|
pi.appendEntry<BtwEntryData>(BTW_ENTRY_TYPE, {
|
|
557
544
|
id: snap.id,
|
|
558
545
|
title: snap.title,
|
|
@@ -562,16 +549,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
562
549
|
});
|
|
563
550
|
return;
|
|
564
551
|
}
|
|
565
|
-
if (consumed)
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
// Keep the result retractable while the parent is working. A later
|
|
570
|
-
// wait_agent can consume it before agent_settled flushes follow-ups.
|
|
571
|
-
// Defer a copy: the live snapshot keeps mutating if the subagent is
|
|
572
|
-
// restarted before the deferred result flushes.
|
|
573
|
-
resultDelivery.defer({ ...snap, meta: { ...snap.meta } });
|
|
574
|
-
if (sessionContext?.isIdle()) flushResults();
|
|
552
|
+
if (consumed) return;
|
|
553
|
+
// Hand the immutable settlement to Pi immediately. Pi queues it when the
|
|
554
|
+
// parent is active and starts a new parent turn when idle.
|
|
555
|
+
deliverResult({ ...snap, meta: { ...snap.meta } });
|
|
575
556
|
};
|
|
576
557
|
|
|
577
558
|
const coordinator: SubagentCoordinator = {
|
|
@@ -641,7 +622,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
641
622
|
);
|
|
642
623
|
|
|
643
624
|
pi.on("session_start", (_event, ctx) => {
|
|
644
|
-
sessionContext = ctx;
|
|
645
625
|
archived.clear();
|
|
646
626
|
peerHistory.length = 0;
|
|
647
627
|
for (const record of loadSubagentCatalog().values()) archived.set(record.id, record);
|
|
@@ -662,12 +642,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
662
642
|
if (ctx.hasUI) ui = ctx.ui;
|
|
663
643
|
});
|
|
664
644
|
|
|
665
|
-
pi.on("agent_settled", flushResults);
|
|
666
|
-
|
|
667
645
|
pi.on("session_shutdown", async () => {
|
|
668
|
-
sessionContext = undefined;
|
|
669
646
|
unsubscribeCoordinator();
|
|
670
|
-
resultDelivery.clear();
|
|
671
647
|
for (const pending of pendingQuestions.values()) {
|
|
672
648
|
pending.reject(new Error("Parent Pi session shut down before replying."));
|
|
673
649
|
}
|
|
@@ -692,7 +668,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
692
668
|
promptSnippet: SUBAGENT_SPAWN_PROMPT_SNIPPET,
|
|
693
669
|
promptGuidelines: [
|
|
694
670
|
...SUBAGENT_SPAWN_PROMPT_GUIDELINES,
|
|
695
|
-
"After spawn_agent starts a child, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch it run;
|
|
671
|
+
"After spawn_agent starts a child, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch it run. Settlement is handed to Pi immediately; when its attached summary invokes the parent, continue the original task without waiting for another user message.",
|
|
696
672
|
],
|
|
697
673
|
parameters: Type.Object({
|
|
698
674
|
message: Type.String({
|
|
@@ -832,7 +808,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
832
808
|
const snapshots = ids.map((id) => manager.view.get(id)!);
|
|
833
809
|
const settled = snapshots.filter((snap) => snap.status !== "running");
|
|
834
810
|
const pending = snapshots.filter((snap) => snap.status === "running");
|
|
835
|
-
resultDelivery.consume(settled.map((snap) => snap.id));
|
|
836
811
|
|
|
837
812
|
const sections: string[] = [];
|
|
838
813
|
let remainingBytes = WAIT_OUTPUT_MAX_BYTES;
|
|
@@ -1121,7 +1096,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1121
1096
|
label: "Start Pi Subagent Tasks",
|
|
1122
1097
|
description: "Start independent subagent tasks together in the background and return their ids immediately. Completion notices automatically start the next parent turn, so the parent should end its current turn when no independent work remains instead of checking status.",
|
|
1123
1098
|
promptGuidelines: [
|
|
1124
|
-
"After task starts children, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch them run;
|
|
1099
|
+
"After task starts children, continue only independent parent work or end the turn immediately. Do not call wait_agent, list_agents, or check_agent merely to watch them run. Settlements are handed to Pi immediately; when their attached summaries invoke the parent, continue the original task without waiting for another user message.",
|
|
1125
1100
|
],
|
|
1126
1101
|
parameters: Type.Object({
|
|
1127
1102
|
tasks: Type.Array(
|
|
@@ -19,6 +19,7 @@ export const SUBAGENT_SPAWN_PROMPT_GUIDELINES = [
|
|
|
19
19
|
"Use resume_from to continue a completed child's existing context instead of restating its original task.",
|
|
20
20
|
"After spawn_agent or task starts background work, continue useful parent work; otherwise end the turn so Pi remains available to the user.",
|
|
21
21
|
"Do not poll background agents. Completion notices arrive automatically and wake the parent; wait_agent only collects results already available and reports running agents without blocking.",
|
|
22
|
+
"When a completion notice invokes the parent, use its attached summary and continue the original task immediately; do not wait for another user message or call a status tool for the same result.",
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
export const SUBAGENT_SPAWN_PARAMETER_DESCRIPTIONS = {
|
|
@@ -51,7 +52,7 @@ export function buildSubagentSpawnResult(options: {
|
|
|
51
52
|
].filter(Boolean);
|
|
52
53
|
return (
|
|
53
54
|
`Started Pi subagent ${options.id} "${options.title}" (${attributes.join(", ")}).\n` +
|
|
54
|
-
"It will report and wake the parent when finished. Continue useful work or end the turn instead of polling."
|
|
55
|
+
"It will report and wake the parent when finished. Continue useful work or end the turn instead of polling; when invoked by the result, continue the original task immediately."
|
|
55
56
|
);
|
|
56
57
|
}
|
|
57
58
|
|
package/package.json
CHANGED
|
@@ -24,7 +24,9 @@ Provide:
|
|
|
24
24
|
- the working directory when it differs from the current directory;
|
|
25
25
|
- an initial wait only when startup output is needed for the next decision.
|
|
26
26
|
|
|
27
|
-
After startup, continue only genuinely useful independent work. If none remains, end the turn immediately.
|
|
27
|
+
After startup, continue only genuinely useful independent work. If none remains, end the turn immediately. Ending the turn is the waiting mechanism: terminal settlement is handed to Pi immediately, queued as a follow-up if the parent is still active, and otherwise starts the next parent turn with the final status and bounded output. Do not keep the current turn alive to wait, invent monitoring work, or call terminal tools merely to see whether the process finished.
|
|
28
|
+
|
|
29
|
+
When that completion follow-up invokes the next turn, treat its attached output as the terminal result and continue the original task immediately. Do not wait for another user message, announce that you are still waiting, or call `read_terminal` to retrieve the same result again. If `start_terminal` itself returns a settled result, the output is already synchronous and no second completion notice is needed.
|
|
28
30
|
|
|
29
31
|
## Inspect and interact only when necessary
|
|
30
32
|
|
|
@@ -24,14 +24,14 @@ This skill governs temporary Pi child agents. The `codex-thread-orchestrator` sk
|
|
|
24
24
|
|
|
25
25
|
## Wait by notification; inspect progress only when justified
|
|
26
26
|
|
|
27
|
-
A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its
|
|
27
|
+
A successful `spawn_agent` or `task` call starts asynchronous work and returns control to the parent. When a child finishes, its settlement is handed to Pi immediately, queued as a follow-up if the parent is still active, and otherwise starts the next main-agent turn. The parent does not need to remain active or check once before ending its turn.
|
|
28
28
|
|
|
29
29
|
After dispatch:
|
|
30
30
|
|
|
31
31
|
1. Continue only parent work that is independently useful to the requested result.
|
|
32
32
|
2. If no such work remains, end the turn immediately. A short progress note is enough when the user needs one.
|
|
33
33
|
3. Do not call `wait_agent`, `list_agents`, or `check_agent` in the same turn merely because the child was just launched. Ending the turn is the waiting mechanism.
|
|
34
|
-
4.
|
|
34
|
+
4. When a child completion notice invokes the main agent, treat the attached summary as the child result and continue the original task immediately. Reconcile completed results, launch any intentionally queued work if capacity requires waves, and otherwise keep waiting through notifications. Do not wait for the user to prompt you again or call a status tool to retrieve the same result.
|
|
35
35
|
|
|
36
36
|
A progress check is reasonable when the user asks for status, a child has run materially longer than expected for its task and model, an interruption left its state unclear, or current status will change an immediate coordination decision. Prefer `check_agent` for one known child and `list_agents` for a batch overview. Use `wait_agent` to collect results already expected to be available, not as a running-status probe.
|
|
37
37
|
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function createDeferredResultDelivery<T extends { id: string }>() {
|
|
2
|
-
const pending = new Map<string, T>();
|
|
3
|
-
|
|
4
|
-
return {
|
|
5
|
-
defer(result: T) {
|
|
6
|
-
pending.set(result.id, result);
|
|
7
|
-
},
|
|
8
|
-
consume(ids: Iterable<string>) {
|
|
9
|
-
for (const id of ids) pending.delete(id);
|
|
10
|
-
},
|
|
11
|
-
drain() {
|
|
12
|
-
const results = [...pending.values()];
|
|
13
|
-
pending.clear();
|
|
14
|
-
return results;
|
|
15
|
-
},
|
|
16
|
-
clear() {
|
|
17
|
-
pending.clear();
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|