@skydiveai/pi-extensions 0.1.0-beta.2894 → 0.1.0-beta.2899

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 +105 -20
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -2359,6 +2359,30 @@ async function postSubagentSpawn({ messageId, tasks }) {
2359
2359
  tasks: body.tasks ?? []
2360
2360
  };
2361
2361
  }
2362
+ async function postSubagentRearm({ messageId, taskId, timeoutMinutes }) {
2363
+ const client = sandboxClient();
2364
+ if (!client) throw new Error("no api url for subagent re-arm");
2365
+ const res = await client.a2a.tasks[":id"].rearm.$post({
2366
+ param: { id: taskId },
2367
+ json: {
2368
+ messageId,
2369
+ timeoutMinutes
2370
+ }
2371
+ });
2372
+ if (!res.ok) {
2373
+ let detail = "";
2374
+ try {
2375
+ const body = await res.json();
2376
+ if (typeof body.error === "string") detail = `: ${body.error}`;
2377
+ } catch {}
2378
+ throw new Error(`subagent re-arm POST failed (${res.status})${detail}`);
2379
+ }
2380
+ const body = await res.json();
2381
+ return {
2382
+ taskId: body.id,
2383
+ deadlineAt: body.deadlineAt
2384
+ };
2385
+ }
2362
2386
  function createHeartbeatThrottle({ messageId }) {
2363
2387
  let lastAt = 0;
2364
2388
  let pending = null;
@@ -4230,7 +4254,7 @@ const soulExtension = (pi) => {
4230
4254
  //#region src/extensions/subagent/index.ts
4231
4255
  const log$1 = logger.child({ module: "subagent-ext" });
4232
4256
  const MAX_TASKS = 8;
4233
- const TaskItem = Type.Object({
4257
+ const SpawnTaskItem = Type.Object({
4234
4258
  task: Type.String({ description: "The task to delegate to a subagent run." }),
4235
4259
  title: Type.String({
4236
4260
  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…\".",
@@ -4244,9 +4268,18 @@ const TaskItem = Type.Object({
4244
4268
  maximum: 360
4245
4269
  }))
4246
4270
  });
4271
+ const RearmTaskItem = Type.Object({
4272
+ taskId: Type.String({ description: "The running subagent task to keep waiting for. Use the taskId returned by an earlier subagent call." }),
4273
+ timeoutMinutes: Type.Integer({
4274
+ description: "Wake again after this many minutes if the subagent is still running. This replaces no work and can be repeated after every timeout. Range 1-360.",
4275
+ minimum: 1,
4276
+ maximum: 360
4277
+ })
4278
+ });
4279
+ const TaskItem = Type.Union([SpawnTaskItem, RearmTaskItem]);
4247
4280
  const DEFAULT_SUBAGENT_TIMEOUT_MS = 30 * 6e4;
4248
4281
  const SubagentParams = Type.Object({ tasks: Type.Array(TaskItem, {
4249
- description: "One or more tasks to delegate. Each spawns an isolated subagent run linked to this conversation; they run in parallel and each rewakes you with its result when it finishes.",
4282
+ description: "Spawn subagent runs with { task, title, ... }, or keep waiting for a running task with { taskId, timeoutMinutes }. Re-arming schedules another parent wake without restarting or stopping the child.",
4250
4283
  minItems: 1,
4251
4284
  maxItems: MAX_TASKS
4252
4285
  }) });
@@ -4256,10 +4289,10 @@ function buildTool(messageId) {
4256
4289
  name: SUBAGENT_TOOL_NAME,
4257
4290
  label: "Subagent",
4258
4291
  description: [
4259
- "Delegate one or more tasks to subagent runs — fresh isolated copies of yourself, each with its own context window, linked to this conversation.",
4292
+ "Delegate tasks to isolated subagent runs, or re-arm a running task so it wakes you again later without stopping the child.",
4260
4293
  "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.",
4261
4294
  "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.",
4262
- "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 — prefer a lower-cost, faster model for well-scoped subtasks that don't need deep reasoning, and reserve a top-tier model for the ones that do; omit it to inherit your own model.",
4295
+ "To spawn, pass { task, title, persona?, model?, timeoutMinutes? }. To keep waiting after a timeout, pass { taskId, timeoutMinutes }; this schedules another wake and leaves the same child running. title is the short human-visible name for new work. persona and model apply only when spawning.",
4263
4296
  "Peering: each queued task comes back with its own conversation id. A subagent is a real linked conversation, so to see what one is doing RIGHT NOW while it runs — its reasoning, the tools it has called and their results, its progress — read that conversation with `platform conversations show <conversationId>` (you are already authorized; it is your own delegated run). Check in that way instead of waiting blind for the final result. The read reflects the child's persisted state, which lags a few seconds behind live (tool results land as they complete; in-progress reasoning can be up to ~5s stale), so peek between checkpoints rather than polling in a tight loop.",
4264
4297
  "Steering: to add context, correct course, or answer a question a subagent needs mid-run, post to its conversation with `platform conversations post <conversationId> --message \"...\"`. If the subagent is still running, your message lands as a live steer picked up in that same turn; if it has gone idle, it queues as its next turn. This is the same primitive as any conversation message — there is no separate steer channel."
4265
4298
  ].join(" "),
@@ -4275,38 +4308,90 @@ function buildTool(messageId) {
4275
4308
  details: {},
4276
4309
  isError: true
4277
4310
  };
4278
- const spawnTasks = tasks.map((t) => ({
4279
- task: t.task,
4280
- title: t.title ?? null,
4281
- persona: t.persona ?? null,
4282
- model: t.model ?? null,
4283
- timeoutMs: t.timeoutMinutes != null ? t.timeoutMinutes * 6e4 : DEFAULT_SUBAGENT_TIMEOUT_MS
4311
+ const spawnRequests = tasks.flatMap((task, index) => "task" in task ? [{
4312
+ task,
4313
+ index
4314
+ }] : []);
4315
+ const rearmRequests = tasks.flatMap((task, index) => "taskId" in task ? [{
4316
+ task,
4317
+ index
4318
+ }] : []);
4319
+ if (spawnRequests.length > 0 && rearmRequests.length > 0) return {
4320
+ content: [{
4321
+ type: "text",
4322
+ text: "A subagent call must either spawn new tasks or re-arm running tasks, not both."
4323
+ }],
4324
+ details: { error: "mixed subagent operations" }
4325
+ };
4326
+ const spawnTasks = spawnRequests.map(({ task }) => ({
4327
+ task: task.task,
4328
+ title: task.title ?? null,
4329
+ persona: task.persona ?? null,
4330
+ model: task.model ?? null,
4331
+ timeoutMs: task.timeoutMinutes != null ? task.timeoutMinutes * 6e4 : DEFAULT_SUBAGENT_TIMEOUT_MS
4284
4332
  }));
4285
4333
  try {
4286
- const spawned = await postSubagentSpawn({
4334
+ const spawned = spawnTasks.length ? await postSubagentSpawn({
4287
4335
  messageId,
4288
4336
  tasks: spawnTasks
4289
- });
4337
+ }) : {
4338
+ taskIds: [],
4339
+ tasks: []
4340
+ };
4341
+ const rearmed = await Promise.all(rearmRequests.map(async ({ task, index }) => {
4342
+ try {
4343
+ const result = await postSubagentRearm({
4344
+ messageId,
4345
+ taskId: task.taskId,
4346
+ timeoutMinutes: task.timeoutMinutes
4347
+ });
4348
+ return {
4349
+ index,
4350
+ timeoutMinutes: task.timeoutMinutes,
4351
+ ...result
4352
+ };
4353
+ } catch (err) {
4354
+ const message = err instanceof Error ? err.message : String(err);
4355
+ log$1.warn({
4356
+ err,
4357
+ event: "subagent_rearm_failed",
4358
+ taskId: task.taskId
4359
+ }, "subagent re-arm failed");
4360
+ return {
4361
+ index,
4362
+ taskId: task.taskId,
4363
+ error: message
4364
+ };
4365
+ }
4366
+ }));
4290
4367
  const { taskIds } = spawned;
4291
4368
  log$1.info({
4292
4369
  event: "subagent_spawned",
4293
4370
  count: taskIds.length
4294
4371
  }, "subagent tasks queued");
4295
4372
  const convByTask = new Map(spawned.tasks.map((t) => [t.taskId, t.conversationId]));
4296
- const lines = taskIds.map((id, i) => {
4297
- const label = spawnTasks[i]?.title ?? spawnTasks[i]?.task ?? "";
4298
- const conv = convByTask.get(id);
4299
- return conv ? `- conversation ${conv}: ${label} (taskId ${id})` : `- ${label} (taskId ${id}, conversation id unavailable — cannot peer this run)`;
4373
+ const lines = tasks.map((_, index) => {
4374
+ const spawnIndex = spawnRequests.findIndex((request) => request.index === index);
4375
+ if (spawnIndex >= 0) {
4376
+ const id = taskIds[spawnIndex];
4377
+ const label = spawnTasks[spawnIndex]?.title ?? spawnTasks[spawnIndex]?.task ?? "";
4378
+ const conv = id ? convByTask.get(id) : void 0;
4379
+ return conv ? `- conversation ${conv}: ${label} (taskId ${id})` : `- ${label} (taskId ${id}, conversation id unavailable — cannot peer this run)`;
4380
+ }
4381
+ const rearm = rearmed.find((request) => request.index === index);
4382
+ if (!rearm) return "- task update unavailable";
4383
+ return "error" in rearm ? `- taskId ${rearm.taskId ?? "unknown"}: re-arm failed — ${rearm.error}` : `- taskId ${rearm.taskId}: wake again in ${rearm.timeoutMinutes} minutes`;
4300
4384
  }).join("\n");
4301
4385
  const peerHint = spawned.tasks.length ? "\nEach subagent runs on its own conversation (id shown per task above). To SEE what one is doing while it runs, read it with `platform conversations show <conversationId>`. To STEER one mid-run — add context, correct course, answer a question — post to its conversation with `platform conversations post <conversationId> --message \"...\"`; it lands as a live steer if the subagent is still running, or as its next turn if it has gone idle." : "";
4302
4386
  return {
4303
4387
  content: [{
4304
4388
  type: "text",
4305
- text: `Queued ${taskIds.length} subagent ${taskIds.length === 1 ? "run" : "runs"}. Each runs on its own and will send you its result on this thread when it finishes — keep working or end your turn meanwhile.\n${lines}${peerHint}`
4389
+ text: `${taskIds.length ? `Queued ${taskIds.length} subagent ${taskIds.length === 1 ? "run" : "runs"}.` : ""}${rearmed.length ? `${taskIds.length ? " " : ""}Re-armed ${rearmed.length} running ${rearmed.length === 1 ? "task" : "tasks"}.` : ""} Each will wake you on completion or at its next timeout.\n${lines}${peerHint}`
4306
4390
  }],
4307
4391
  details: {
4308
4392
  taskIds,
4309
- tasks: spawned.tasks
4393
+ tasks: spawned.tasks,
4394
+ rearmed
4310
4395
  }
4311
4396
  };
4312
4397
  } catch (err) {
@@ -4314,11 +4399,11 @@ function buildTool(messageId) {
4314
4399
  log$1.warn({
4315
4400
  err,
4316
4401
  event: "subagent_spawn_failed"
4317
- }, "subagent spawn failed");
4402
+ }, "subagent request failed");
4318
4403
  return {
4319
4404
  content: [{
4320
4405
  type: "text",
4321
- text: `Failed to queue subagent runs: ${message}`
4406
+ text: `Failed to update subagent tasks: ${message}`
4322
4407
  }],
4323
4408
  details: {},
4324
4409
  isError: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-extensions",
3
- "version": "0.1.0-beta.2894",
3
+ "version": "0.1.0-beta.2899",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",