@signalridge/pi-goal 1.4.7 → 1.5.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/CHANGELOG.md +10 -0
- package/README.md +7 -3
- package/package.json +1 -1
- package/src/lifecycle.ts +26 -4
- package/src/prompts.ts +19 -0
- package/src/safety.ts +9 -0
- package/src/settings-ui.ts +29 -1
- package/src/settings.ts +18 -3
- package/src/tools.ts +15 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @signalridge/pi-goal
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- 4697441: Add `experimental.notices`, a setting beside `experimental.goals` that silences the session-start notice announcing the experimental ordered-goal queue. It defaults to `true`, so existing sessions report exactly as before, and it suppresses only that announcement: validation failures, settings-load warnings, and recovery notices still report because they describe a problem rather than restate a choice the user already made. The queue, its subcommands, and `queue off` freezing are unaffected, and the Settings menu exposes the new control directly under **Ordered goal queue**.
|
|
7
|
+
|
|
8
|
+
## 1.4.8
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 0b24158: Report safety-pause causes and current count/limit before ambiguous ownership errors in Goal tools, and include an authoritative request-local pause notice when background notifications trigger ordinary conversation. Keep ownership, stale-ID, and abort guards intact: notifications never reset counters or bypass a safety pause, and only explicit resume restarts the Goal.
|
|
12
|
+
|
|
3
13
|
## 1.4.7
|
|
4
14
|
### Patch Changes
|
|
5
15
|
|
package/README.md
CHANGED
|
@@ -61,7 +61,8 @@ built-in defaults without creating the file:
|
|
|
61
61
|
{
|
|
62
62
|
"toolVisibility": "after-first-goal",
|
|
63
63
|
"experimental": {
|
|
64
|
-
"goals": false
|
|
64
|
+
"goals": false,
|
|
65
|
+
"notices": true
|
|
65
66
|
},
|
|
66
67
|
"rpc": {
|
|
67
68
|
"enabled": false
|
|
@@ -74,13 +75,14 @@ built-in defaults without creating the file:
|
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
Use `/goal` → **Settings…** in the TUI to create or update the file interactively, or create
|
|
77
|
-
and edit it directly. The standard Settings screen keeps all
|
|
78
|
+
and edit it directly. The standard Settings screen keeps all six controls on one level in task
|
|
78
79
|
order; the two safety limits open standard choice screens:
|
|
79
80
|
|
|
80
81
|
- **Automatic-work limit** shows the exact response limit or **Unlimited**. Choose **Set response limit…** to edit the current finite value (or the built-in default of 25 when switching from Unlimited), or choose **Unlimited…**. Unlimited requires confirmation that tool loops may continue consuming tokens and provider cost without a response-count cap.
|
|
81
82
|
- **No-progress guard** shows **_N_ runs** or **Off**. Choose the default threshold, **Off**, or **Set threshold…** and enter a safe whole number greater than zero.
|
|
82
83
|
- **Goal tools** controls whether terminal Goal tools are always visible or appear after the first goal.
|
|
83
84
|
- **Ordered goal queue** controls the experimental ordered-goal workflows.
|
|
85
|
+
- **Experimental notice** controls whether a session reports once that experimental behavior and persisted state may change. It defaults to **On** and suppresses only that announcement; the queue itself is unaffected.
|
|
84
86
|
- **Managed run RPC** controls whether trusted installed extensions may start and cancel managed Goal runs. It defaults to **Off** and is a cooperation setting, not an extension security sandbox.
|
|
85
87
|
|
|
86
88
|
Custom number inputs reject zero, negative numbers, decimals, text, and unsafe integers without saving; use the explicit **Unlimited** or **Off** choice instead. Interactive changes are serialized, written atomically, preserve unknown fields, and apply to the current runtime. A successful change updates the visible state immediately. A failed save restores the prior value and reports the settings path so it can be retried. Tool-visibility changes that would alter the active tool schema are rejected while Pi is busy; retry after Pi settles. Escape returns to the previous screen without reverting changes that were already saved.
|
|
@@ -92,6 +94,8 @@ Custom number inputs reject zero, negative numbers, decimals, text, and unsafe i
|
|
|
92
94
|
|
|
93
95
|
`experimental.goals` accepts a boolean and defaults to `false`. Set it to `true` to enable the ordered-goal subcommands and automatic queue advancement described below. Enabled sessions show one warning because command behavior and persisted queue state remain experimental.
|
|
94
96
|
|
|
97
|
+
`experimental.notices` accepts a boolean and defaults to `true`. It controls that single experimental-goals announcement: with `notices: false` a session starts without it, while the queue, its subcommands, and queue freezing behave exactly as they do with `notices: true`. Nothing else routes through this setting — validation failures, settings-load warnings, and recovery notices always report, because those describe a problem rather than restate a choice the user has already made. Use it when the experiment is a standing configuration rather than a trial, so the warning level keeps meaning something.
|
|
98
|
+
|
|
95
99
|
`rpc.enabled` accepts a boolean and defaults to `false`. When disabled, a valid managed-run start receives `RPC_DISABLED`; manual and restored Goals remain unchanged. A Settings-menu change applies immediately after its atomic save. Disabling rejects new starts but lets an already accepted run continue publishing its exact state and accept its exact cancellation until terminal, avoiding stranded work. Reload, replacement, and shutdown clear that in-memory ownership.
|
|
96
100
|
|
|
97
101
|
`continuationLimits` controls the runaway guards:
|
|
@@ -167,7 +171,7 @@ Goal state is stored as Pi session state, similar to Codex's thread-owned goals.
|
|
|
167
171
|
|
|
168
172
|
A `goal_wait` pause stores its reason and optional absolute wake deadline in the same session entry. On reload or session resume, a future deadline is re-armed; an overdue deadline gets one resume attempt after Pi is idle and pending messages have drained. No timer runs while Pi is closed. A wait without a deadline stays quiet until real interactive or RPC input is accepted for delivery, or `/goal resume` is used. Input consumed by another extension or rejected before the turn starts leaves the wait intact. Accepted real input resumes only a recorded wait and supplies the turn itself, without an extra resume prompt. A queued follow-up wakes the wait when delivered, not when queued. If delivery stays inside an existing agent run, the context hook supplies the rotated goal ID before its next response; it does not add another user turn. Extension-generated input and `/goal` commands do not count as real-input wake triggers. When Pi cannot distinguish real and extension input at delivery (for example, identical queued text or cross-transformed messages), the goal stays waiting rather than accepting a possible extension wake. That ambiguity remains attached to the waiting goal, including its deadline, until explicit `/goal resume` or reload; later input is not used to guess which earlier message was delivered. Automatic wakes do not override a restrictive active-tool policy; an overdue deadline rechecks readiness at most once per minute until Goal tools are available, while a settled boundary may wake sooner. An input whose delivery has not been confirmed also defers the deadline rather than racing a second prompt; if that input was rejected, resolve the failure and use `/goal resume` explicitly.
|
|
169
173
|
|
|
170
|
-
Ordinary pauses, safety pauses, blockers, usage limits, exhausted budgets, and frozen queues do not auto-resume. Older paused entries without wait metadata remain paused; the extension does not infer a wait from transcript text. `/goal pause`, edit, clear, and replacement cancel the old wait. A synchronous deadline prompt-submission failure restores the stopped goal and wait metadata without a timer retry loop; use `/goal resume` or reload after resolving the problem. Pi's extension API does not acknowledge asynchronous prompt acceptance, so a later authentication or model failure cannot be rolled back by the extension; if no turn starts, inspect `/goal status` and explicitly pause/resume or reload after fixing the failure. Objective, cumulative usage, and the configured finite or Unlimited automatic-work setting are retained.
|
|
174
|
+
Ordinary pauses, safety pauses, blockers, usage limits, exhausted budgets, and frozen queues do not auto-resume. Background completion notifications can trigger ordinary conversation while a Goal is safety-paused; they do not resume Goal execution, rotate its ID, or reset its counters. Each model request during a safety pause receives a request-local authoritative state notice with the cause, current count/limit, and explicit-resume requirement, superseding earlier active Goal instructions. Goal tools report that safety-pause cause before ownership or stale-ID diagnostics; those guards still apply once the Goal is active. Use `/goal resume` (or the manager's Continue action) to restart. Older paused entries without wait metadata remain paused; the extension does not infer a wait from transcript text. `/goal pause`, edit, clear, and replacement cancel the old wait. A synchronous deadline prompt-submission failure restores the stopped goal and wait metadata without a timer retry loop; use `/goal resume` or reload after resolving the problem. Pi's extension API does not acknowledge asynchronous prompt acceptance, so a later authentication or model failure cannot be rolled back by the extension; if no turn starts, inspect `/goal status` and explicitly pause/resume or reload after fixing the failure. Objective, cumulative usage, and the configured finite or Unlimited automatic-work setting are retained.
|
|
171
175
|
|
|
172
176
|
Ordered queues use the same canonical `goal-state` session entry as single goals. Every item owns independent usage and safety state. Shelving, priority displacement, automatic advancement, and later reactivation preserve that item's epoch rather than granting more automatic work. The legacy `{ goal }` shape remains valid, and missing safety fields normalize to zero/defaults. Queue fields are written only when needed. Sessions created by the former standalone `pi-goals` experiment can migrate their last `goals-state` array and pending `unshift` intent when the branch has never written a canonical `goal-state`; any canonical entry, including an explicit clear, takes precedence so old plural state cannot be resurrected.
|
|
173
177
|
|
package/package.json
CHANGED
package/src/lifecycle.ts
CHANGED
|
@@ -6,8 +6,10 @@ import { type ActiveGoal, loadGoalStateFromSession } from "./persistence.js";
|
|
|
6
6
|
import {
|
|
7
7
|
buildGoalBindingUpdate,
|
|
8
8
|
buildGoalPrompt,
|
|
9
|
+
buildGoalSafetyPauseNotice,
|
|
9
10
|
buildGoalSystemPrompt,
|
|
10
11
|
GOAL_BINDING_UPDATE_HEADER,
|
|
12
|
+
GOAL_SAFETY_PAUSE_CONTEXT_TYPE,
|
|
11
13
|
} from "./prompts.js";
|
|
12
14
|
import { activateQueuedGoal } from "./queue.js";
|
|
13
15
|
import type { GoalRunController } from "./run-protocol.js";
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
type StatusContext,
|
|
29
31
|
truncateNotification,
|
|
30
32
|
} from "./runtime.js";
|
|
31
|
-
import { hasAssistantToolCall } from "./safety.js";
|
|
33
|
+
import { formatGoalSafetyPause, hasAssistantToolCall } from "./safety.js";
|
|
32
34
|
import { DEFAULT_GOAL_SETTINGS, readGoalSettings } from "./settings.js";
|
|
33
35
|
|
|
34
36
|
const EXPERIMENTAL_GOALS_WARNING =
|
|
@@ -104,7 +106,7 @@ export function registerGoalLifecycle(
|
|
|
104
106
|
if (settingsResult.kind === "invalid") {
|
|
105
107
|
notifyTerminal(ctx.ui, `pi-goal settings ignored: ${settingsResult.reason}. Using default settings.`, "warning");
|
|
106
108
|
}
|
|
107
|
-
if (runtime.settings.experimental.goals) {
|
|
109
|
+
if (runtime.settings.experimental.goals && runtime.settings.experimental.notices) {
|
|
108
110
|
notifyTerminal(ctx.ui, EXPERIMENTAL_GOALS_WARNING, "warning");
|
|
109
111
|
}
|
|
110
112
|
try {
|
|
@@ -533,13 +535,31 @@ export function registerGoalLifecycle(
|
|
|
533
535
|
});
|
|
534
536
|
|
|
535
537
|
pi.on("context", (event, ctx) => {
|
|
536
|
-
const messages = event.messages.filter(
|
|
538
|
+
const messages = event.messages.filter(
|
|
539
|
+
(message) =>
|
|
540
|
+
runtime.keepBudgetWrapUpMessage(message) &&
|
|
541
|
+
!(message.role === "custom" && message.customType === GOAL_SAFETY_PAUSE_CONTEXT_TYPE),
|
|
542
|
+
);
|
|
537
543
|
if (runtime.activeGoal?.status === "paused" && runtime.guardAbortGoalId === runtime.activeGoal.id) {
|
|
538
544
|
// A current custom follow-up clears the guard at message_start. Otherwise,
|
|
539
545
|
// context transformation aborts before the provider adapter receives the signal.
|
|
540
546
|
abortCurrentTurn(ctx);
|
|
541
547
|
}
|
|
542
548
|
const goal = runtime.activeGoal;
|
|
549
|
+
const safetyPause =
|
|
550
|
+
!runtime.queueFrozen && goal ? buildGoalSafetyPauseNotice(goal, runtime.settings.continuationLimits) : undefined;
|
|
551
|
+
if (safetyPause) {
|
|
552
|
+
// Request-local, so queued custom notifications (which can skip
|
|
553
|
+
// before_agent_start) also see current state without persisting stale notices.
|
|
554
|
+
messages.push({
|
|
555
|
+
role: "custom",
|
|
556
|
+
customType: GOAL_SAFETY_PAUSE_CONTEXT_TYPE,
|
|
557
|
+
content: safetyPause,
|
|
558
|
+
display: false,
|
|
559
|
+
timestamp: Date.now(),
|
|
560
|
+
});
|
|
561
|
+
return { messages };
|
|
562
|
+
}
|
|
543
563
|
if (!runtime.queueFrozen && goal?.status === "active" && runtime.inputWakeGoalId === goal.id) {
|
|
544
564
|
// Native queued input reaches message_start without before_agent_start.
|
|
545
565
|
// Keep the rotated guard visible for every remaining response in this run,
|
|
@@ -595,7 +615,9 @@ export function registerGoalLifecycle(
|
|
|
595
615
|
abortCurrentTurn(ctx);
|
|
596
616
|
return {
|
|
597
617
|
block: true,
|
|
598
|
-
reason:
|
|
618
|
+
reason:
|
|
619
|
+
formatGoalSafetyPause(runtime.activeGoal, runtime.settings.continuationLimits) ??
|
|
620
|
+
"Blocked stale /goal tool call after the goal stopped or was interrupted.",
|
|
599
621
|
};
|
|
600
622
|
});
|
|
601
623
|
|
package/src/prompts.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { formatTokenCount } from "./accounting.js";
|
|
2
|
+
import type { ActiveGoal } from "./persistence.js";
|
|
3
|
+
import { formatGoalSafetyPause } from "./safety.js";
|
|
4
|
+
import type { GoalSettings } from "./settings.js";
|
|
5
|
+
|
|
6
|
+
export const GOAL_SAFETY_PAUSE_CONTEXT_TYPE = "goal-safety-pause-state";
|
|
7
|
+
|
|
8
|
+
export function buildGoalSafetyPauseNotice(goal: ActiveGoal, limits: GoalSettings["continuationLimits"]) {
|
|
9
|
+
const pause = formatGoalSafetyPause(goal, limits);
|
|
10
|
+
if (!pause) return undefined;
|
|
11
|
+
return [
|
|
12
|
+
"Goal runtime state (authoritative for this request):",
|
|
13
|
+
`goal_id: ${escapeXmlText(goal.id)}`,
|
|
14
|
+
pause,
|
|
15
|
+
"This state supersedes earlier active Goal instructions. This response is ordinary conversation or notification handling, not Goal automatic execution.",
|
|
16
|
+
"Do not claim the Goal is running or continuing automatically. Do not call goal_complete, goal_blocked, or goal_wait while it is paused.",
|
|
17
|
+
"Background completion notifications and ordinary messages do not resume this safety pause or reset its counters. Only explicit user resume can restart Goal execution.",
|
|
18
|
+
].join("\n");
|
|
19
|
+
}
|
|
2
20
|
|
|
3
21
|
export type GoalStatus = "active" | "queued" | "paused" | "blocked" | "usage_limited" | "budget_limited" | "complete";
|
|
4
22
|
|
|
@@ -67,6 +85,7 @@ function goalCompletionGuardBlock(goal: GoalPromptContext) {
|
|
|
67
85
|
function goalModeRules(goalLabel: string) {
|
|
68
86
|
return [
|
|
69
87
|
"Goal-mode rules:",
|
|
88
|
+
"- Automatic continuation requires active Goal runtime state. A later authoritative Goal runtime state notice supersedes earlier active instructions; never treat notification handling as proof that Goal execution resumed.",
|
|
70
89
|
"- Preserve the full objective across turns; do not redefine success around a narrower, safer, smaller, merely compatible, or easier-to-test result.",
|
|
71
90
|
"- Derive concrete requirements from the objective and any referenced files, plans, specifications, issues, or user instructions.",
|
|
72
91
|
"- Treat the current worktree, command output, tests, runtime behavior, PR state, rendered artifacts, and external state as authoritative. Previous conversation, plans, and summaries are context, not proof; inspect the current state before relying on them.",
|
package/src/safety.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { ActiveGoal } from "./persistence.js";
|
|
3
|
+
import type { GoalSettings } from "./settings.js";
|
|
4
|
+
|
|
5
|
+
export function formatGoalSafetyPause(goal: ActiveGoal, limits: GoalSettings["continuationLimits"]) {
|
|
6
|
+
if (goal.status !== "paused" || !goal.safetyPauseCause) return undefined;
|
|
7
|
+
const automatic = goal.safetyPauseCause === "continuation_limit";
|
|
8
|
+
const used = automatic ? goal.automaticModelTurns : goal.toolFreeRepeatCount;
|
|
9
|
+
const limit = automatic ? limits.automaticTurns : limits.noProgressTurns;
|
|
10
|
+
return `Goal paused: ${goal.safetyPauseCause} (${used}/${limit ?? "Unlimited"}). Explicit resume required: run /goal resume.`;
|
|
11
|
+
}
|
|
3
12
|
|
|
4
13
|
export interface ToolFreeRepeatState {
|
|
5
14
|
toolFreeRepeatCount: number;
|
package/src/settings-ui.ts
CHANGED
|
@@ -43,6 +43,7 @@ export async function showGoalSettings(
|
|
|
43
43
|
| "choose-no-progress"
|
|
44
44
|
| "set-visibility"
|
|
45
45
|
| "set-queue"
|
|
46
|
+
| "set-notices"
|
|
46
47
|
| "set-rpc";
|
|
47
48
|
const menu = defineMenu<undefined, Screen, Action, ExtensionCommandContext>({
|
|
48
49
|
start: invalid ? "invalid" : (options.initialScreen ?? "settings"),
|
|
@@ -82,6 +83,14 @@ export async function showGoalSettings(
|
|
|
82
83
|
values: ["Off", "Experimental"],
|
|
83
84
|
action: "set-queue",
|
|
84
85
|
},
|
|
86
|
+
{
|
|
87
|
+
id: "experimentalNotices",
|
|
88
|
+
label: "Experimental notice",
|
|
89
|
+
description: "Say once per session that experimental behavior and persisted state may change.",
|
|
90
|
+
currentValue: runtime.settings.experimental.notices ? "On" : "Off",
|
|
91
|
+
values: ["On", "Off"],
|
|
92
|
+
action: "set-notices",
|
|
93
|
+
},
|
|
85
94
|
{
|
|
86
95
|
id: "rpcEnabled",
|
|
87
96
|
label: "Managed run RPC",
|
|
@@ -104,6 +113,7 @@ export async function showGoalSettings(
|
|
|
104
113
|
`No-progress guard: ${formatNoProgressProtection(runtime.settings.continuationLimits.noProgressTurns)}`,
|
|
105
114
|
`Goal tools: ${visibilityLabel(runtime.settings.toolVisibility)}`,
|
|
106
115
|
`Ordered goal queue: ${runtime.settings.experimental.goals ? "Experimental" : "Off"}`,
|
|
116
|
+
`Experimental notice: ${runtime.settings.experimental.notices ? "On" : "Off"}`,
|
|
107
117
|
`Managed run RPC: ${runtime.settings.rpc.enabled ? "On" : "Off"}`,
|
|
108
118
|
],
|
|
109
119
|
hint: "back",
|
|
@@ -186,6 +196,24 @@ export async function showGoalSettings(
|
|
|
186
196
|
return { kind: "rejected" };
|
|
187
197
|
}
|
|
188
198
|
},
|
|
199
|
+
"set-notices": async ({ value }) => {
|
|
200
|
+
const enabled = value === "On";
|
|
201
|
+
if (enabled === runtime.settings.experimental.notices) return { kind: "stay" };
|
|
202
|
+
try {
|
|
203
|
+
const next = {
|
|
204
|
+
...structuredClone(runtime.settings),
|
|
205
|
+
experimental: { ...runtime.settings.experimental, notices: enabled },
|
|
206
|
+
} satisfies GoalSettings;
|
|
207
|
+
applyGoalSettings(runtime, next, ctx, {
|
|
208
|
+
save: (settings) => (options.save ?? saveGoalSettings)(settings, settingsPath),
|
|
209
|
+
});
|
|
210
|
+
notifyTerminal(ctx.ui, `Experimental notice: ${enabled ? "On" : "Off"}.`, "info");
|
|
211
|
+
return { kind: "stay" };
|
|
212
|
+
} catch (error) {
|
|
213
|
+
notifySettingsFailure(ctx, settingsPath, error);
|
|
214
|
+
return { kind: "rejected" };
|
|
215
|
+
}
|
|
216
|
+
},
|
|
189
217
|
"set-rpc": async ({ value }) => {
|
|
190
218
|
const enabled = value === "On";
|
|
191
219
|
if (enabled === runtime.settings.rpc.enabled) return { kind: "stay" };
|
|
@@ -459,7 +487,7 @@ async function nextQueueSettings(runtime: GoalRuntime, ctx: ExtensionCommandCont
|
|
|
459
487
|
}
|
|
460
488
|
return {
|
|
461
489
|
...structuredClone(runtime.settings),
|
|
462
|
-
experimental: { goals: enabled },
|
|
490
|
+
experimental: { ...runtime.settings.experimental, goals: enabled },
|
|
463
491
|
} satisfies GoalSettings;
|
|
464
492
|
}
|
|
465
493
|
|
package/src/settings.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface GoalSettings {
|
|
|
13
13
|
toolVisibility: GoalToolVisibility;
|
|
14
14
|
experimental: {
|
|
15
15
|
goals: boolean;
|
|
16
|
+
notices: boolean;
|
|
16
17
|
};
|
|
17
18
|
rpc: {
|
|
18
19
|
enabled: boolean;
|
|
@@ -32,7 +33,12 @@ export const DEFAULT_GOAL_SETTINGS: GoalSettings = {
|
|
|
32
33
|
// unfinished goal reveals them at startup. Set `"always"` to keep them
|
|
33
34
|
// resident.
|
|
34
35
|
toolVisibility: "after-first-goal",
|
|
35
|
-
|
|
36
|
+
// `notices` is on by default: a user who enabled an experiment should be told
|
|
37
|
+
// once per session that its persisted state may change. It exists so that
|
|
38
|
+
// acknowledgement is a setting rather than a repeated prompt — a warning the
|
|
39
|
+
// reader cannot accept stops carrying information, and the same notification
|
|
40
|
+
// level carries failures worth reading.
|
|
41
|
+
experimental: { goals: false, notices: true },
|
|
36
42
|
rpc: { enabled: false },
|
|
37
43
|
continuationLimits: { automaticTurns: 25, noProgressTurns: 3 },
|
|
38
44
|
};
|
|
@@ -70,6 +76,11 @@ export function normalizeGoalSettings(value: unknown): GoalSettings | undefined
|
|
|
70
76
|
? Reflect.get(experimentalValue, "goals")
|
|
71
77
|
: DEFAULT_GOAL_SETTINGS.experimental.goals;
|
|
72
78
|
if (typeof goals !== "boolean") return undefined;
|
|
79
|
+
const notices =
|
|
80
|
+
experimentalValue && Object.hasOwn(experimentalValue, "notices")
|
|
81
|
+
? Reflect.get(experimentalValue, "notices")
|
|
82
|
+
: DEFAULT_GOAL_SETTINGS.experimental.notices;
|
|
83
|
+
if (typeof notices !== "boolean") return undefined;
|
|
73
84
|
|
|
74
85
|
const rpcValue = Object.hasOwn(value, "rpc") ? Reflect.get(value, "rpc") : undefined;
|
|
75
86
|
if (rpcValue !== undefined && (typeof rpcValue !== "object" || rpcValue === null || Array.isArray(rpcValue))) {
|
|
@@ -108,7 +119,7 @@ export function normalizeGoalSettings(value: unknown): GoalSettings | undefined
|
|
|
108
119
|
|
|
109
120
|
return {
|
|
110
121
|
toolVisibility: toolVisibility as GoalToolVisibility,
|
|
111
|
-
experimental: { goals },
|
|
122
|
+
experimental: { goals, notices },
|
|
112
123
|
rpc: { enabled: rpcEnabled },
|
|
113
124
|
continuationLimits: { automaticTurns, noProgressTurns },
|
|
114
125
|
};
|
|
@@ -149,7 +160,11 @@ export function saveGoalSettings(
|
|
|
149
160
|
{
|
|
150
161
|
...raw,
|
|
151
162
|
toolVisibility: normalized.toolVisibility,
|
|
152
|
-
experimental: {
|
|
163
|
+
experimental: {
|
|
164
|
+
...experimental,
|
|
165
|
+
goals: normalized.experimental.goals,
|
|
166
|
+
notices: normalized.experimental.notices,
|
|
167
|
+
},
|
|
153
168
|
rpc: { ...rpc, enabled: normalized.rpc.enabled },
|
|
154
169
|
continuationLimits: {
|
|
155
170
|
...continuationLimits,
|
package/src/tools.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
transitionGoal,
|
|
21
21
|
truncateNotification,
|
|
22
22
|
} from "./runtime.js";
|
|
23
|
+
import { formatGoalSafetyPause } from "./safety.js";
|
|
23
24
|
import {
|
|
24
25
|
createGoalWait,
|
|
25
26
|
MAX_GOAL_WAIT_REASON_LENGTH,
|
|
@@ -101,6 +102,15 @@ export function registerGoalTools(pi: ExtensionAPI, runtime: GoalRuntime) {
|
|
|
101
102
|
};
|
|
102
103
|
}
|
|
103
104
|
const completingDuringBudgetWrapUp = runtime.hasActiveBudgetWrapUp();
|
|
105
|
+
const safetyPause = formatGoalSafetyPause(completedGoal, runtime.settings.continuationLimits);
|
|
106
|
+
if (safetyPause) {
|
|
107
|
+
const rejection = `Goal completion rejected: ${safetyPause}`;
|
|
108
|
+
notifyTerminal(ctx.ui, rejection, "warning");
|
|
109
|
+
return {
|
|
110
|
+
content: toolContent(rejection),
|
|
111
|
+
details: completionDetails(goal, requestedGoalId, summary),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
104
114
|
if (!runtime.canRecordGoalUsage() && !completingDuringBudgetWrapUp) {
|
|
105
115
|
const rejection = "Goal completion rejected: current run does not own the active goal.";
|
|
106
116
|
notifyTerminal(ctx.ui, rejection, "warning");
|
|
@@ -257,7 +267,7 @@ export function registerGoalTools(pi: ExtensionAPI, runtime: GoalRuntime) {
|
|
|
257
267
|
const evidence = typeof params.evidence === "string" ? params.evidence.trim() : "";
|
|
258
268
|
const repeatedTurns = typeof params.repeated_turns === "number" ? params.repeated_turns : Number.NaN;
|
|
259
269
|
const reject = (rejectionReason: string, terminate = false) => {
|
|
260
|
-
const rejection = `goal_blocked rejected: ${rejectionReason}
|
|
270
|
+
const rejection = `goal_blocked rejected: ${rejectionReason}${rejectionReason.endsWith(".") ? "" : "."}`;
|
|
261
271
|
notifyTerminal(ctx.ui, rejection, "warning");
|
|
262
272
|
return {
|
|
263
273
|
content: toolContent(rejection),
|
|
@@ -267,6 +277,8 @@ export function registerGoalTools(pi: ExtensionAPI, runtime: GoalRuntime) {
|
|
|
267
277
|
};
|
|
268
278
|
|
|
269
279
|
if (!blockedGoal) return reject("no active goal");
|
|
280
|
+
const safetyPause = formatGoalSafetyPause(blockedGoal, runtime.settings.continuationLimits);
|
|
281
|
+
if (safetyPause) return reject(safetyPause);
|
|
270
282
|
if (!runtime.canRecordGoalUsage()) {
|
|
271
283
|
return reject("current run does not own the active goal");
|
|
272
284
|
}
|
|
@@ -363,6 +375,8 @@ export function registerGoalTools(pi: ExtensionAPI, runtime: GoalRuntime) {
|
|
|
363
375
|
};
|
|
364
376
|
|
|
365
377
|
if (!goal) return reject("no active goal");
|
|
378
|
+
const safetyPause = formatGoalSafetyPause(goal, runtime.settings.continuationLimits);
|
|
379
|
+
if (safetyPause) return reject(safetyPause);
|
|
366
380
|
if (!runtime.canRecordGoalUsage()) return reject("current run does not own the active goal");
|
|
367
381
|
if (hasPendingSkipForGoal(runtime, goal.id)) {
|
|
368
382
|
runtime.recordGoalUsage(goal, ctx);
|