@kenkaiiii/ggcoder 4.3.210 → 4.3.212

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 (34) hide show
  1. package/dist/core/goal-prerequisites.d.ts +18 -0
  2. package/dist/core/goal-prerequisites.d.ts.map +1 -0
  3. package/dist/core/goal-prerequisites.js +77 -0
  4. package/dist/core/goal-prerequisites.js.map +1 -0
  5. package/dist/core/goal-prerequisites.test.d.ts +2 -0
  6. package/dist/core/goal-prerequisites.test.d.ts.map +1 -0
  7. package/dist/core/goal-prerequisites.test.js +90 -0
  8. package/dist/core/goal-prerequisites.test.js.map +1 -0
  9. package/dist/core/goal-store.d.ts.map +1 -1
  10. package/dist/core/goal-store.js +11 -2
  11. package/dist/core/goal-store.js.map +1 -1
  12. package/dist/core/goal-store.test.js +14 -6
  13. package/dist/core/goal-store.test.js.map +1 -1
  14. package/dist/core/prompt-commands.d.ts.map +1 -1
  15. package/dist/core/prompt-commands.js +5 -4
  16. package/dist/core/prompt-commands.js.map +1 -1
  17. package/dist/core/prompt-commands.test.js +4 -0
  18. package/dist/core/prompt-commands.test.js.map +1 -1
  19. package/dist/tools/goals.d.ts.map +1 -1
  20. package/dist/tools/goals.js +49 -9
  21. package/dist/tools/goals.js.map +1 -1
  22. package/dist/tools/goals.test.js +43 -0
  23. package/dist/tools/goals.test.js.map +1 -1
  24. package/dist/ui/App.d.ts +12 -2
  25. package/dist/ui/App.d.ts.map +1 -1
  26. package/dist/ui/App.js +73 -63
  27. package/dist/ui/App.js.map +1 -1
  28. package/dist/ui/app-state-persistence.test.js +8 -8
  29. package/dist/ui/app-state-persistence.test.js.map +1 -1
  30. package/dist/ui/goal-lifecycle-orchestration.test.js +19 -28
  31. package/dist/ui/goal-lifecycle-orchestration.test.js.map +1 -1
  32. package/dist/ui/goal-overlay.test.js +1 -1
  33. package/dist/ui/goal-overlay.test.js.map +1 -1
  34. package/package.json +5 -5
package/dist/ui/App.js CHANGED
@@ -61,6 +61,7 @@ import { getMCPServers } from "../core/mcp/index.js";
61
61
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
62
62
  import { appendGoalDecision, appendGoalEvidence, formatGoalBlockingPrerequisites, goalHasBlockingPrerequisites, loadGoalRuns, reconcileActiveGoalRuns, summarizeGoalCounts, summarizeGoalCountsFromRuns, updateGoalTask, upsertGoalRun, } from "../core/goal-store.js";
63
63
  import { canCompleteGoalRun, decideGoalNextAction } from "../core/goal-controller.js";
64
+ import { runGoalPrerequisiteChecks } from "../core/goal-prerequisites.js";
64
65
  import { runGoalVerifierCommand } from "../core/goal-verifier.js";
65
66
  import { listGoalWorkers, startGoalWorker, stopGoalWorker, subscribeGoalWorkerCompletions, } from "../core/goal-worker.js";
66
67
  import { formatGoalVerifierCompletionEvent, formatGoalWorkerCompletionEvent, isGoalSyntheticEvent, parseGoalSyntheticEvent, } from "./goal-events.js";
@@ -266,6 +267,16 @@ function goalProgressMatchesDraft(item, draft) {
266
267
  item.status === draft.status &&
267
268
  JSON.stringify(item.summaryRows ?? []) === JSON.stringify(draft.summaryRows ?? []));
268
269
  }
270
+ /**
271
+ * Reconcile terminal Goal cards that are already visible in this UI session.
272
+ *
273
+ * This intentionally does not synthesize missing cards from durable GoalRun
274
+ * state. Goal terminal cards are event notifications: they should appear when
275
+ * the terminal event happens in the current UI, not whenever a fresh session
276
+ * polls old Goal runs from the Goal pane. Callers that just observed a terminal
277
+ * event append that card first, then use this helper to tombstone stale older
278
+ * cards for the same run.
279
+ */
269
280
  export function completedItemsWithDurableGoalTerminalProgress(items, runs) {
270
281
  const runIds = new Set(runs.map((run) => run.id));
271
282
  const terminalByRun = new Map(runs
@@ -273,29 +284,18 @@ export function completedItemsWithDurableGoalTerminalProgress(items, runs) {
273
284
  .filter((entry) => entry[1] !== null));
274
285
  if (runIds.size === 0)
275
286
  return items;
276
- const upToDateRunIds = new Set();
277
287
  let changed = false;
278
288
  const reconciled = items.map((item, index) => {
279
289
  const runId = goalTerminalRunIdFromItem(item);
280
290
  if (!runId || !runIds.has(runId))
281
291
  return item;
282
292
  const draft = terminalByRun.get(runId);
283
- if (draft && goalProgressMatchesDraft(item, draft)) {
284
- upToDateRunIds.add(runId);
293
+ if (draft && goalProgressMatchesDraft(item, draft))
285
294
  return item;
286
- }
287
295
  changed = true;
288
296
  return { kind: "tombstone", id: `tombstone-${item.id}-${index}` };
289
297
  });
290
- const additions = [];
291
- for (const [runId, progress] of terminalByRun) {
292
- if (upToDateRunIds.has(runId))
293
- continue;
294
- additions.push({ ...progress, id: goalTerminalProgressId({ id: runId }) });
295
- }
296
- if (!changed && additions.length === 0)
297
- return items;
298
- return [...reconciled, ...additions];
298
+ return changed ? reconciled : items;
299
299
  }
300
300
  export function formatGoalTerminalProgress(run) {
301
301
  switch (run.status) {
@@ -344,17 +344,16 @@ export function formatGoalTerminalProgress(run) {
344
344
  return null;
345
345
  }
346
346
  }
347
- export function shouldHideHistoryForOverlayView(_isOverlayView, _isAgentRunning) {
348
- // Ink Static is append-only. Passing [] for overlay panes rewrites the Static
349
- // accumulator and can destroy scrollback when the pane closes. Keep history
350
- // mounted and let overlays render below it.
351
- return false;
347
+ export function shouldHideHistoryForOverlayView(isOverlayView, _isAgentRunning) {
348
+ // Overlay panes are standalone full-screen states. Do not render chat Static
349
+ // history behind them, otherwise panes appear below the previous transcript.
350
+ return isOverlayView;
352
351
  }
353
352
  export function shouldStabilizeOverlayPaneRerender({ overlayPane, isAgentRunning, }) {
354
353
  return isAgentRunning && (overlayPane === "goal" || overlayPane === "plan");
355
354
  }
356
- export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender, }) {
357
- return shouldHideHistoryForOverlay && !stabilizeOverlayPaneRerender;
355
+ export function shouldHideStaticItemsForOverlayView({ shouldHideHistoryForOverlay, stabilizeOverlayPaneRerender: _stabilizeOverlayPaneRerender, }) {
356
+ return shouldHideHistoryForOverlay;
358
357
  }
359
358
  export function getScrollStabilizationDecision({ isUserScrolled, hasNewOutput, hasTallLiveUserMessage = false, }) {
360
359
  const shouldStabilize = isUserScrolled || hasTallLiveUserMessage;
@@ -2953,111 +2952,121 @@ export function App(props) {
2953
2952
  const startGoalRun = useCallback((run) => {
2954
2953
  runningGoalIdsRef.current.add(run.id);
2955
2954
  void (async () => {
2956
- if (goalHasBlockingPrerequisites(run)) {
2955
+ const currentRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
2956
+ const prereqCheck = await runGoalPrerequisiteChecks(props.cwd, currentRun);
2957
+ const checkedRun = prereqCheck.checkedCount > 0
2958
+ ? await upsertGoalRun(props.cwd, {
2959
+ ...prereqCheck.run,
2960
+ status: goalHasBlockingPrerequisites(prereqCheck.run) ? "blocked" : "ready",
2961
+ })
2962
+ : currentRun;
2963
+ if (goalHasBlockingPrerequisites(checkedRun)) {
2957
2964
  setOverlay(null);
2958
- const detail = formatGoalBlockingPrerequisites(run);
2965
+ const detail = formatGoalBlockingPrerequisites(checkedRun);
2959
2966
  await upsertGoalRun(props.cwd, {
2960
- ...run,
2967
+ ...checkedRun,
2961
2968
  status: "blocked",
2962
- blockers: Array.from(new Set([...run.blockers, detail])),
2969
+ blockers: Array.from(new Set([...checkedRun.blockers, detail])),
2963
2970
  });
2964
2971
  setGoalCount((await summarizeGoalCounts(props.cwd)).active);
2965
2972
  appendGoalProgress({
2966
2973
  kind: "goal_progress",
2967
2974
  phase: "terminal",
2968
- title: `Goal blocked: ${run.title}`,
2975
+ title: `Goal blocked: ${checkedRun.title}`,
2969
2976
  detail,
2970
2977
  status: "blocked",
2971
2978
  });
2972
- runningGoalIdsRef.current.delete(run.id);
2973
- clearGoalStatusEntry(run.id);
2979
+ runningGoalIdsRef.current.delete(checkedRun.id);
2980
+ clearGoalStatusEntry(checkedRun.id);
2974
2981
  return;
2975
2982
  }
2976
- const decision = decideGoalNextAction(run);
2977
- await appendGoalDecision(props.cwd, run.id, decision);
2983
+ const decision = decideGoalNextAction(checkedRun);
2984
+ await appendGoalDecision(props.cwd, checkedRun.id, decision);
2978
2985
  if (decision.kind === "terminal") {
2979
- const terminalProgress = formatGoalTerminalProgress(run);
2986
+ const terminalProgress = formatGoalTerminalProgress(checkedRun);
2980
2987
  if (terminalProgress) {
2981
- const item = { ...terminalProgress, id: goalTerminalProgressId(run) };
2982
- setLiveItems((prev) => completedItemsWithDurableGoalTerminalProgress([...prev, item], [run]));
2988
+ const item = { ...terminalProgress, id: goalTerminalProgressId(checkedRun) };
2989
+ setLiveItems((prev) => completedItemsWithDurableGoalTerminalProgress([...prev, item], [checkedRun]));
2983
2990
  }
2984
- runningGoalIdsRef.current.delete(run.id);
2985
- clearGoalStatusEntry(run.id);
2991
+ runningGoalIdsRef.current.delete(checkedRun.id);
2992
+ clearGoalStatusEntry(checkedRun.id);
2986
2993
  return;
2987
2994
  }
2988
2995
  if (decision.kind === "wait") {
2989
2996
  appendGoalProgress({
2990
2997
  kind: "goal_progress",
2991
2998
  phase: "worker_started",
2992
- title: decision.workerId ? `Goal working: ${run.title}` : `Goal active: ${run.title}`,
2999
+ title: decision.workerId
3000
+ ? `Goal working: ${checkedRun.title}`
3001
+ : `Goal active: ${checkedRun.title}`,
2993
3002
  detail: decision.reason,
2994
3003
  workerId: decision.workerId,
2995
3004
  });
2996
3005
  upsertGoalStatusEntry({
2997
- runId: run.id,
2998
- label: run.title,
3006
+ runId: checkedRun.id,
3007
+ label: checkedRun.title,
2999
3008
  phase: decision.workerId ? "worker" : "orchestrating",
3000
3009
  startedAt: Date.now(),
3001
3010
  detail: decision.reason,
3002
3011
  workerId: decision.workerId,
3003
- goalNumber: goalNumberForRun(run.id),
3012
+ goalNumber: goalNumberForRun(checkedRun.id),
3004
3013
  });
3005
3014
  return;
3006
3015
  }
3007
3016
  if (decision.kind === "complete") {
3008
- await upsertGoalRun(props.cwd, { ...run, status: "passed" });
3017
+ await upsertGoalRun(props.cwd, { ...checkedRun, status: "passed" });
3009
3018
  setGoalCount((await summarizeGoalCounts(props.cwd)).active);
3010
3019
  appendGoalProgress({
3011
3020
  kind: "goal_progress",
3012
3021
  phase: "terminal",
3013
- title: `Goal passed: ${run.title}`,
3022
+ title: `Goal passed: ${checkedRun.title}`,
3014
3023
  detail: decision.reason,
3015
3024
  status: "passed",
3016
3025
  });
3017
- runningGoalIdsRef.current.delete(run.id);
3018
- clearGoalStatusEntry(run.id);
3026
+ runningGoalIdsRef.current.delete(checkedRun.id);
3027
+ clearGoalStatusEntry(checkedRun.id);
3019
3028
  return;
3020
3029
  }
3021
3030
  if (decision.kind === "run_verifier") {
3022
- await verifyGoalRun(run);
3031
+ await verifyGoalRun(checkedRun);
3023
3032
  return;
3024
3033
  }
3025
3034
  if (decision.kind === "create_task") {
3026
- await updateGoalTask(props.cwd, run.id, `auto-${Date.now()}`, {
3035
+ await updateGoalTask(props.cwd, checkedRun.id, `auto-${Date.now()}`, {
3027
3036
  title: decision.title,
3028
3037
  prompt: decision.prompt,
3029
3038
  status: "pending",
3030
3039
  });
3031
- const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? run;
3040
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ?? checkedRun;
3032
3041
  await upsertGoalRun(props.cwd, { ...latestRun, status: "ready" });
3033
- setTimeout(() => continueGoalRun(run.id), 250);
3042
+ setTimeout(() => continueGoalRun(checkedRun.id), 250);
3034
3043
  return;
3035
3044
  }
3036
3045
  if (decision.kind === "blocked") {
3037
3046
  await upsertGoalRun(props.cwd, {
3038
- ...run,
3047
+ ...checkedRun,
3039
3048
  status: "blocked",
3040
- blockers: [...run.blockers, decision.reason],
3049
+ blockers: [...checkedRun.blockers, decision.reason],
3041
3050
  });
3042
3051
  setGoalCount((await summarizeGoalCounts(props.cwd)).active);
3043
3052
  appendGoalProgress({
3044
3053
  kind: "goal_progress",
3045
3054
  phase: "terminal",
3046
- title: `Goal blocked: ${run.title}`,
3055
+ title: `Goal blocked: ${checkedRun.title}`,
3047
3056
  detail: decision.reason,
3048
3057
  status: "blocked",
3049
3058
  });
3050
- runningGoalIdsRef.current.delete(run.id);
3051
- clearGoalStatusEntry(run.id);
3059
+ runningGoalIdsRef.current.delete(checkedRun.id);
3060
+ clearGoalStatusEntry(checkedRun.id);
3052
3061
  return;
3053
3062
  }
3054
3063
  if (decision.kind === "pause") {
3055
- const runWithBlockedTask = (await updateGoalTask(props.cwd, run.id, decision.task.id, {
3064
+ const runWithBlockedTask = (await updateGoalTask(props.cwd, checkedRun.id, decision.task.id, {
3056
3065
  status: "blocked",
3057
3066
  attempts: decision.attempts,
3058
3067
  lastSummary: "Paused after worker attempt limit.",
3059
- })) ?? run;
3060
- const runWithPauseEvidence = (await appendGoalEvidence(props.cwd, run.id, {
3068
+ })) ?? checkedRun;
3069
+ const runWithPauseEvidence = (await appendGoalEvidence(props.cwd, checkedRun.id, {
3061
3070
  kind: "summary",
3062
3071
  label: "Goal paused",
3063
3072
  content: decision.reason,
@@ -3072,27 +3081,28 @@ export function App(props) {
3072
3081
  appendGoalProgress({
3073
3082
  kind: "goal_progress",
3074
3083
  phase: "terminal",
3075
- title: `Goal paused: ${run.title}`,
3084
+ title: `Goal paused: ${checkedRun.title}`,
3076
3085
  detail: decision.reason,
3077
3086
  status: "paused",
3078
3087
  });
3079
- runningGoalIdsRef.current.delete(run.id);
3080
- clearGoalStatusEntry(run.id);
3088
+ runningGoalIdsRef.current.delete(checkedRun.id);
3089
+ clearGoalStatusEntry(checkedRun.id);
3081
3090
  return;
3082
3091
  }
3083
- const runWithAttempt = (await updateGoalTask(props.cwd, run.id, decision.task.id, {
3092
+ const runWithAttempt = (await updateGoalTask(props.cwd, checkedRun.id, decision.task.id, {
3084
3093
  attempts: decision.attempts,
3085
- })) ?? run;
3094
+ })) ?? checkedRun;
3086
3095
  const worker = await startGoalWorker({
3087
3096
  cwd: props.cwd,
3088
3097
  provider: currentProvider,
3089
3098
  model: currentModel,
3090
- goalRunId: run.id,
3099
+ goalRunId: checkedRun.id,
3091
3100
  goalTaskId: decision.task.id,
3092
3101
  taskTitle: decision.task.title,
3093
3102
  prompt: decision.task.prompt,
3094
3103
  });
3095
- const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === run.id) ?? runWithAttempt;
3104
+ const latestRun = (await loadGoalRuns(props.cwd)).find((item) => item.id === checkedRun.id) ??
3105
+ runWithAttempt;
3096
3106
  await upsertGoalRun(props.cwd, {
3097
3107
  ...latestRun,
3098
3108
  status: "running",
@@ -3113,13 +3123,13 @@ export function App(props) {
3113
3123
  status: worker.status,
3114
3124
  });
3115
3125
  upsertGoalStatusEntry({
3116
- runId: run.id,
3126
+ runId: checkedRun.id,
3117
3127
  label: decision.task.title,
3118
3128
  phase: "worker",
3119
3129
  startedAt: Date.now(),
3120
3130
  detail: "background worker running",
3121
3131
  workerId: worker.id,
3122
- goalNumber: goalNumberForRun(run.id),
3132
+ goalNumber: goalNumberForRun(checkedRun.id),
3123
3133
  });
3124
3134
  })().catch((err) => {
3125
3135
  clearGoalStatusEntry(run.id);