@skydiveai/pi-extensions 0.1.0-beta.8 → 0.1.0-beta.80
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/dist/index.mjs +51 -11
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1949,11 +1949,33 @@ const subscribers = {
|
|
|
1949
1949
|
subagent: /* @__PURE__ */ new Set()
|
|
1950
1950
|
};
|
|
1951
1951
|
let pollerStarted = false;
|
|
1952
|
+
let firstPollSettled = false;
|
|
1953
|
+
let resolveFirstPoll = null;
|
|
1954
|
+
const firstPollPromise = new Promise((resolve) => {
|
|
1955
|
+
resolveFirstPoll = resolve;
|
|
1956
|
+
});
|
|
1957
|
+
function markFirstPollSettled() {
|
|
1958
|
+
if (firstPollSettled) return;
|
|
1959
|
+
firstPollSettled = true;
|
|
1960
|
+
resolveFirstPoll?.();
|
|
1961
|
+
}
|
|
1952
1962
|
/** Last-polled value of a flag, or `null` if not yet resolved. */
|
|
1953
1963
|
function getPolledFlag(name) {
|
|
1954
1964
|
return name === "contextManagement" ? contextManagement : subagent;
|
|
1955
1965
|
}
|
|
1956
1966
|
/**
|
|
1967
|
+
* Await the first poll already kicked by `startFeatureFlagPoller` (never a new
|
|
1968
|
+
* GET). Resolves when that poll settles, immediately if it already has, or
|
|
1969
|
+
* immediately when there's no flag source to poll. Callers on the hot path
|
|
1970
|
+
* should race this against their own short timeout so a slow/failed flag
|
|
1971
|
+
* service cannot delay first-token; a timeout just means the caller reads the
|
|
1972
|
+
* still-cold cache and falls back to its default, exactly as before.
|
|
1973
|
+
*/
|
|
1974
|
+
function awaitFirstFlagPoll() {
|
|
1975
|
+
if (firstPollSettled || !hasFlagSource()) return Promise.resolve();
|
|
1976
|
+
return firstPollPromise;
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1957
1979
|
* Subscribe to changes of a flag. The callback fires only on a *transition*
|
|
1958
1980
|
* (skipped while the value is unchanged), so a subscriber registered before the
|
|
1959
1981
|
* first poll still learns the initial value. Returns an unsubscribe fn.
|
|
@@ -1977,10 +1999,14 @@ function apply(name, next) {
|
|
|
1977
1999
|
}
|
|
1978
2000
|
}
|
|
1979
2001
|
async function pollOnce() {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2002
|
+
try {
|
|
2003
|
+
const flags = await fetchHarnessFlags();
|
|
2004
|
+
if (!flags) return;
|
|
2005
|
+
apply("contextManagement", flags.contextManagement ?? null);
|
|
2006
|
+
apply("subagent", flags.subagent ?? null);
|
|
2007
|
+
} catch (err) {
|
|
2008
|
+
log$9.debug({ err }, "feature-flag poll threw");
|
|
2009
|
+
}
|
|
1984
2010
|
}
|
|
1985
2011
|
/**
|
|
1986
2012
|
* Start the shared background poll (idempotent). No-op when there's no
|
|
@@ -1991,7 +2017,7 @@ async function pollOnce() {
|
|
|
1991
2017
|
function startFeatureFlagPoller() {
|
|
1992
2018
|
if (pollerStarted || !hasFlagSource()) return;
|
|
1993
2019
|
pollerStarted = true;
|
|
1994
|
-
pollOnce();
|
|
2020
|
+
pollOnce().finally(markFirstPollSettled);
|
|
1995
2021
|
setInterval(() => void pollOnce(), FLAG_POLL_INTERVAL_MS).unref?.();
|
|
1996
2022
|
}
|
|
1997
2023
|
//#endregion
|
|
@@ -2755,9 +2781,14 @@ const soulExtension = (pi) => {
|
|
|
2755
2781
|
//#endregion
|
|
2756
2782
|
//#region src/extensions/subagent/index.ts
|
|
2757
2783
|
const log$2 = logger.child({ module: "subagent-ext" });
|
|
2784
|
+
const COLD_START_FLAG_WAIT_MS = 750;
|
|
2758
2785
|
const MAX_TASKS = 8;
|
|
2759
2786
|
const TaskItem = Type.Object({
|
|
2760
2787
|
task: Type.String({ description: "The task to delegate to a subagent run." }),
|
|
2788
|
+
title: Type.String({
|
|
2789
|
+
description: "A SHORT name for this task — 3-6 words, sentence case, no trailing period. This is what the person in the chat sees as the row for this subagent, so name the work, do not restate the prompt. Good: \"Audit the billing gate\", \"Compare competitor pricing\", \"Draft the migration\". Bad: \"You are looking at apps/anyone/web and should check every component…\".",
|
|
2790
|
+
maxLength: 120
|
|
2791
|
+
}),
|
|
2761
2792
|
persona: Type.Optional(Type.String({ description: "Optional extra system prompt / role for this task, applied ON TOP of the child run's own default persona (your full identity and soul are still there underneath). Omit to run with just your default persona." })),
|
|
2762
2793
|
model: Type.Optional(Type.String({ description: "Optional model id to run this subagent on (e.g. \"anthropic/claude-opus-4-8\"). Must be a real catalogued model. Omit to run on your own model. If you are locked to a Google-compliant model, only compliant models are accepted." }))
|
|
2763
2794
|
});
|
|
@@ -2775,7 +2806,7 @@ function buildTool(messageId) {
|
|
|
2775
2806
|
"Delegate one or more tasks to subagent runs — fresh isolated copies of yourself, each with its own context window, linked to this conversation.",
|
|
2776
2807
|
"Use it to parallelize independent work, to keep a large or noisy subtask out of your own context, or to run a task under a specialized persona.",
|
|
2777
2808
|
"Fire-and-forget: this returns immediately after queueing. It does NOT wait for results. Each subagent runs on its own and, when it finishes, sends you its result on this thread — so queue the work, then keep going or end your turn. To chain, re-delegate after a result lands.",
|
|
2778
|
-
"Pass tasks: [{ task, persona?, model? }]. persona is an optional extra system prompt layered ON TOP of your default persona for that task (it adds to, it does not replace, your identity); omit it to run with just your default persona. model is an optional model id for that task; omit it to run on your own model."
|
|
2809
|
+
"Pass tasks: [{ task, title, persona?, model? }]. title is a short 3-6 word name for the task — it is shown to the person in the chat as that subagent's row, so name the work rather than restating the prompt. persona is an optional extra system prompt layered ON TOP of your default persona for that task (it adds to, it does not replace, your identity); omit it to run with just your default persona. model is an optional model id for that task; omit it to run on your own model."
|
|
2779
2810
|
].join(" "),
|
|
2780
2811
|
promptSnippet: "subagent — delegate tasks to isolated subagent runs; each rewakes you with its result when done",
|
|
2781
2812
|
parameters: SubagentParams,
|
|
@@ -2791,6 +2822,7 @@ function buildTool(messageId) {
|
|
|
2791
2822
|
};
|
|
2792
2823
|
const spawnTasks = tasks.map((t) => ({
|
|
2793
2824
|
task: t.task,
|
|
2825
|
+
title: t.title ?? null,
|
|
2794
2826
|
persona: t.persona ?? null,
|
|
2795
2827
|
model: t.model ?? null
|
|
2796
2828
|
}));
|
|
@@ -2803,7 +2835,7 @@ function buildTool(messageId) {
|
|
|
2803
2835
|
event: "subagent_spawned",
|
|
2804
2836
|
count: taskIds.length
|
|
2805
2837
|
}, "subagent tasks queued");
|
|
2806
|
-
const lines = taskIds.map((id, i) => `- ${id}: ${spawnTasks[i]?.task ?? ""}`).join("\n");
|
|
2838
|
+
const lines = taskIds.map((id, i) => `- ${id}: ${spawnTasks[i]?.title ?? spawnTasks[i]?.task ?? ""}`).join("\n");
|
|
2807
2839
|
return {
|
|
2808
2840
|
content: [{
|
|
2809
2841
|
type: "text",
|
|
@@ -2840,11 +2872,19 @@ function createSubagentExtension({ channelContext }) {
|
|
|
2840
2872
|
return (pi) => {
|
|
2841
2873
|
const messageId = extractMessageId(channelContext);
|
|
2842
2874
|
startFeatureFlagPoller();
|
|
2875
|
+
let registered = false;
|
|
2876
|
+
const registerOnce = () => {
|
|
2877
|
+
if (registered) return;
|
|
2878
|
+
registered = true;
|
|
2879
|
+
pi.registerTool(buildTool(messageId));
|
|
2880
|
+
log$2.info({ event: "subagent_enabled" }, "subagent tool registered");
|
|
2881
|
+
};
|
|
2882
|
+
onFlagChange("subagent", (enabled) => {
|
|
2883
|
+
if (enabled) registerOnce();
|
|
2884
|
+
});
|
|
2843
2885
|
pi.on("session_start", async () => {
|
|
2844
|
-
if (getPolledFlag("subagent") ===
|
|
2845
|
-
|
|
2846
|
-
log$2.info({ event: "subagent_enabled" }, "subagent tool registered");
|
|
2847
|
-
}
|
|
2886
|
+
if (getPolledFlag("subagent") === null) await Promise.race([awaitFirstFlagPoll(), new Promise((resolve) => setTimeout(resolve, COLD_START_FLAG_WAIT_MS).unref?.())]);
|
|
2887
|
+
if (getPolledFlag("subagent") === true) registerOnce();
|
|
2848
2888
|
});
|
|
2849
2889
|
};
|
|
2850
2890
|
}
|