@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.
Files changed (2) hide show
  1. package/dist/index.mjs +51 -11
  2. 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
- const flags = await fetchHarnessFlags();
1981
- if (!flags) return;
1982
- apply("contextManagement", flags.contextManagement ?? null);
1983
- apply("subagent", flags.subagent ?? null);
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") === true) {
2845
- pi.registerTool(buildTool(messageId));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-extensions",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.1.0-beta.80",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",