@zhushanwen/pi-goal 0.1.6 → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-goal",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "Codex-style /goal command for Pi — persistent goal-driven autonomous loop with evidence-based completion, token/time budgets, blocked detection, and steering templates.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -28,10 +28,12 @@ import {
28
28
  MAX_HISTORY_ENTRIES,
29
29
  } from "./constants";
30
30
  import {
31
+ createInitialState,
31
32
  deserializeState,
32
33
  getCompletedCount,
33
34
  isActiveStatus,
34
35
  isTerminalStatus,
36
+ type GoalExternalInit,
35
37
  } from "./state";
36
38
  import {
37
39
  executeGoalAction,
@@ -41,6 +43,7 @@ import {
41
43
  HISTORY_ENTRY_TYPE,
42
44
  isGoalEntry,
43
45
  isStaleContextError,
46
+ persistGoalState,
44
47
  updateWidget,
45
48
  } from "./tool-handler";
46
49
  import { toSingleLine } from "./widget";
@@ -180,7 +183,8 @@ export default function goalExtension(pi: ExtensionAPI) {
180
183
  isProcessing: false, // P1-3: 防重入
181
184
  };
182
185
 
183
- // ── Tool: goal_manager ─────────────────────────────
186
+ // Capture latest ctx for external API (initializeGoalFromExternal needs it for persistGoalState)
187
+ let lastCtx: ExtensionContext | undefined;
184
188
 
185
189
  pi.registerTool({
186
190
  name: "goal_manager",
@@ -217,6 +221,7 @@ export default function goalExtension(pi: ExtensionAPI) {
217
221
  parameters: GoalManagerParams,
218
222
 
219
223
  async execute(_toolCallId: string, params: Static<typeof GoalManagerParams>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: ExtensionContext) {
224
+ lastCtx = ctx;
220
225
  try {
221
226
  // P1-4: 透传 signal
222
227
  return await executeGoalAction(pi, session, params, ctx, signal);
@@ -303,6 +308,7 @@ export default function goalExtension(pi: ExtensionAPI) {
303
308
 
304
309
  // ── Event: before_agent_start ──────────────────────
305
310
  pi.on("before_agent_start", async (_event: BeforeAgentStartLikeEvent, ctx: ExtensionContext) => {
311
+ lastCtx = ctx;
306
312
  return handleBeforeAgentStart(pi, session, ctx);
307
313
  });
308
314
 
@@ -345,6 +351,7 @@ export default function goalExtension(pi: ExtensionAPI) {
345
351
 
346
352
  // ── Event: session_start (state reconstruction) ───
347
353
  pi.on("session_start", async (_event: SessionStartLikeEvent, ctx: ExtensionContext) => {
354
+ lastCtx = ctx;
348
355
  reconstructGoalState(pi, session, ctx);
349
356
  if (session.state) {
350
357
  session.tasksCompletedAtAgentStart = getCompletedCount(session.state.tasks);
@@ -372,4 +379,45 @@ export default function goalExtension(pi: ExtensionAPI) {
372
379
  return new Text(prefix + theme.fg("dim", content), 0, 0);
373
380
  });
374
381
  }
382
+
383
+ // ── External API: initializeGoalFromExternal ──────────
384
+
385
+ /**
386
+ * Allow other extensions (e.g. coding-workflow) to programmatically initialize a goal.
387
+ * Skips the /goal command flow — directly creates state + tasks.
388
+ * Returns true if initialized, false if goal already active.
389
+ */
390
+ function initializeGoalFromExternal(
391
+ objective: string,
392
+ tasks: string[],
393
+ budget?: { tokenBudget?: number; timeBudgetMinutes?: number; maxTurns?: number },
394
+ ): boolean {
395
+ if (session.state && isActiveStatus(session.state.status)) {
396
+ return false;
397
+ }
398
+
399
+ session.state = createInitialState(objective, budget);
400
+ session.tasksCompletedAtAgentStart = 0;
401
+
402
+ // Create tasks (same logic as handleCreateTasks)
403
+ session.state.tasks = tasks.map((desc, i) => ({
404
+ id: i + 1,
405
+ description: desc.length > 60 ? desc.slice(0, 57) + "..." : desc,
406
+ status: "pending" as const,
407
+ lastUpdatedTurn: session.state!.currentTurnIndex,
408
+ }));
409
+
410
+ // Persist state so it survives session reconstruction
411
+ // Note: ctx is captured from the last event handler invocation — acceptable
412
+ // because initializeGoalFromExternal is called synchronously during tool execution.
413
+ if (lastCtx) {
414
+ persistGoalState(pi, session, lastCtx);
415
+ }
416
+
417
+ return true;
418
+ }
419
+
420
+ // Expose on pi for cross-extension access
421
+ const api = pi as unknown as Record<string, unknown>;
422
+ api.__goalInit = initializeGoalFromExternal satisfies GoalExternalInit;
375
423
  }
package/src/state.ts CHANGED
@@ -15,6 +15,25 @@
15
15
 
16
16
  import { MS_PER_SECOND } from "./constants";
17
17
 
18
+ // ── Cross-extension API type ─────────────────────────────
19
+
20
+ /** Budget options for external goal initialization. */
21
+ export interface GoalExternalBudget {
22
+ tokenBudget?: number;
23
+ timeBudgetMinutes?: number;
24
+ maxTurns?: number;
25
+ }
26
+
27
+ /**
28
+ * Function signature for programmatically initializing a goal from another extension.
29
+ * Exposed via `pi.__goalInit` by the goal extension.
30
+ */
31
+ export type GoalExternalInit = (
32
+ objective: string,
33
+ tasks: string[],
34
+ budget?: GoalExternalBudget,
35
+ ) => boolean;
36
+
18
37
  // ── Goal 状态枚举 ──────────────────────────────────────
19
38
 
20
39
  export type GoalStatus =