gestalt-mobile 0.29.0 → 0.30.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/dist/client/assets/{index-BtlbAtm9.css → index-C79AKZvE.css} +1 -1
- package/dist/client/assets/index-mKzhyiTl.js +33 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/composition.js +32 -4
- package/dist/server/server/features/autopilot/application/policy.js +11 -2
- package/dist/server/server/features/autopilot/application/service.js +237 -12
- package/dist/server/server/features/autopilot/domain/supervised-lifecycle.js +11 -1
- package/dist/server/server/features/autopilot/domain/supervision-protocol.js +203 -0
- package/dist/server/server/features/sessions/interaction/kind.js +1 -0
- package/dist/server/server/features/sessions/interaction/response-validator.js +4 -1
- package/dist/server/server/platform/codex/server-request.js +13 -0
- package/dist/server/server/platform/codex/session-runtime.js +3 -0
- package/dist/server/server/platform/persistence/sqlite-autopilot-store.js +11 -2
- package/dist/server/server/platform/plans/filesystem-plan-status-source.js +35 -0
- package/dist/server/shared/contracts/autopilot-wait-lease.js +62 -0
- package/package.json +1 -1
- package/dist/client/assets/index-CRULnRRB.js +0 -33
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
/** Facts which may wake a parked supervisor without inspecting conversation text. */
|
|
7
|
+
export const observableWakeConditions = [
|
|
8
|
+
'planChanged',
|
|
9
|
+
'reviewChanged',
|
|
10
|
+
'checkpointChanged',
|
|
11
|
+
'interactionChanged',
|
|
12
|
+
'executorChanged',
|
|
13
|
+
'processExited',
|
|
14
|
+
'processResultAvailable',
|
|
15
|
+
'processLimitBreached',
|
|
16
|
+
'agentActivityChanged',
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* A canonical, durable representation rather than a clock- or transcript-derived score.
|
|
20
|
+
* The string is intentionally opaque to callers; compare it only for equality.
|
|
21
|
+
*/
|
|
22
|
+
export function semanticProgressKey(facts) {
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
plan: facts.plan,
|
|
25
|
+
review: facts.review,
|
|
26
|
+
checkpoint: facts.checkpoint,
|
|
27
|
+
pendingInteractions: [...facts.pendingInteractions]
|
|
28
|
+
.map(({ id, kind, state }) => ({ id, kind, state }))
|
|
29
|
+
.sort(compareBy('id')),
|
|
30
|
+
executor: facts.executor,
|
|
31
|
+
ownedProcesses: [...facts.ownedProcesses]
|
|
32
|
+
.map(({ id, state, ownerGeneration }) => ({ id, state, ownerGeneration }))
|
|
33
|
+
.sort(compareBy('id')),
|
|
34
|
+
agentActivity: latestAgentActivity(facts.agentActivity),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function latestAgentActivity(observations) {
|
|
38
|
+
const latest = new Map();
|
|
39
|
+
for (const observation of observations) {
|
|
40
|
+
const current = latest.get(observation.agentId);
|
|
41
|
+
if (!current ||
|
|
42
|
+
observation.sequence > current.sequence ||
|
|
43
|
+
(observation.sequence === current.sequence &&
|
|
44
|
+
observation.state.localeCompare(current.state) < 0))
|
|
45
|
+
latest.set(observation.agentId, observation);
|
|
46
|
+
}
|
|
47
|
+
return [...latest.values()]
|
|
48
|
+
.map(({ agentId, sequence, state }) => ({ agentId, sequence, state }))
|
|
49
|
+
.sort(compareBy('agentId'));
|
|
50
|
+
}
|
|
51
|
+
function compareBy(key) {
|
|
52
|
+
return (left, right) => left[key].localeCompare(right[key]);
|
|
53
|
+
}
|
|
54
|
+
export function startSupervisionProtocol(progressKey) {
|
|
55
|
+
return {
|
|
56
|
+
outcome: 'active',
|
|
57
|
+
progressKey,
|
|
58
|
+
unchangedContinuations: 0,
|
|
59
|
+
probeKey: null,
|
|
60
|
+
lastReportId: null,
|
|
61
|
+
waitLease: null,
|
|
62
|
+
retryKey: null,
|
|
63
|
+
safetyPauseReason: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Records an automatic continuation result; prose and timestamps are not inputs. */
|
|
67
|
+
export function recordAutomaticContinuation(state, progressKey) {
|
|
68
|
+
if (state.outcome === 'parked' ||
|
|
69
|
+
state.outcome === 'attentionRequired' ||
|
|
70
|
+
state.outcome === 'safetyPaused')
|
|
71
|
+
return state;
|
|
72
|
+
// The probe itself is the one continuation allowed while this key is
|
|
73
|
+
// unchanged. Reaching the scheduling boundary again without a structured
|
|
74
|
+
// report or semantic progress means that probe failed closed.
|
|
75
|
+
if (state.outcome === 'probeRequired' && progressKey === state.probeKey)
|
|
76
|
+
return { ...state, outcome: 'safetyPaused', safetyPauseReason: 'invalidProbeReport' };
|
|
77
|
+
if (state.outcome === 'retrying' && progressKey === state.retryKey)
|
|
78
|
+
return state.unchangedContinuations === 0
|
|
79
|
+
? { ...state, unchangedContinuations: 1 }
|
|
80
|
+
: { ...state, outcome: 'safetyPaused', safetyPauseReason: 'retryRecurrence' };
|
|
81
|
+
if (progressKey !== state.progressKey)
|
|
82
|
+
return { ...startSupervisionProtocol(progressKey), lastReportId: state.lastReportId };
|
|
83
|
+
const unchangedContinuations = Math.min(3, state.unchangedContinuations + 1);
|
|
84
|
+
if (unchangedContinuations === 3 && state.probeKey !== progressKey)
|
|
85
|
+
return { ...state, unchangedContinuations, outcome: 'probeRequired', probeKey: progressKey };
|
|
86
|
+
return { ...state, unchangedContinuations };
|
|
87
|
+
}
|
|
88
|
+
/** Accepts only structured, bounded probe reports. Unsupported waits fail closed. */
|
|
89
|
+
export function reportProbe(state, report) {
|
|
90
|
+
if (state.lastReportId === report.id)
|
|
91
|
+
return state;
|
|
92
|
+
if (state.outcome !== 'probeRequired' || !state.probeKey)
|
|
93
|
+
return state;
|
|
94
|
+
if (report.kind === 'attention')
|
|
95
|
+
return { ...state, outcome: 'attentionRequired', lastReportId: report.id };
|
|
96
|
+
if (report.kind === 'actionable')
|
|
97
|
+
return {
|
|
98
|
+
...startSupervisionProtocol(state.progressKey),
|
|
99
|
+
lastReportId: report.id,
|
|
100
|
+
};
|
|
101
|
+
const wakeConditions = uniqueSupportedWakeConditions(report.wakeConditions);
|
|
102
|
+
if (!report.leaseId || wakeConditions.length === 0)
|
|
103
|
+
return state;
|
|
104
|
+
return {
|
|
105
|
+
...state,
|
|
106
|
+
outcome: 'parked',
|
|
107
|
+
lastReportId: report.id,
|
|
108
|
+
waitLease: { id: report.leaseId, probeKey: state.probeKey, wakeConditions },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** A wake is useful only when it matches the durable lease and changed semantic state. */
|
|
112
|
+
export function consumeObservableWake(state, wake) {
|
|
113
|
+
const lease = state.waitLease;
|
|
114
|
+
if (state.outcome !== 'parked' ||
|
|
115
|
+
!lease ||
|
|
116
|
+
wake.leaseId !== lease.id ||
|
|
117
|
+
!lease.wakeConditions.includes(wake.condition) ||
|
|
118
|
+
wake.progressKey === lease.probeKey)
|
|
119
|
+
return state;
|
|
120
|
+
return {
|
|
121
|
+
...state,
|
|
122
|
+
outcome: 'retrying',
|
|
123
|
+
progressKey: wake.progressKey,
|
|
124
|
+
unchangedContinuations: 0,
|
|
125
|
+
probeKey: null,
|
|
126
|
+
waitLease: null,
|
|
127
|
+
retryKey: wake.progressKey,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Manual recovery is the only transition out of durable safety pause. */
|
|
131
|
+
export function recoverSafetyPause(state, progressKey) {
|
|
132
|
+
return state.outcome === 'safetyPaused' ? startSupervisionProtocol(progressKey) : state;
|
|
133
|
+
}
|
|
134
|
+
/** Reject malformed persisted values instead of silently resuming automatic work. */
|
|
135
|
+
export function parseSupervisionProtocolState(value) {
|
|
136
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
137
|
+
return undefined;
|
|
138
|
+
const candidate = value;
|
|
139
|
+
const base = startSupervisionProtocol(typeof candidate.progressKey === 'string' ? candidate.progressKey : '');
|
|
140
|
+
if (!base.progressKey || !isProtocolOutcome(candidate.outcome))
|
|
141
|
+
return undefined;
|
|
142
|
+
const unchangedContinuations = candidate.unchangedContinuations;
|
|
143
|
+
if (unchangedContinuations !== 0 &&
|
|
144
|
+
unchangedContinuations !== 1 &&
|
|
145
|
+
unchangedContinuations !== 2 &&
|
|
146
|
+
unchangedContinuations !== 3)
|
|
147
|
+
return undefined;
|
|
148
|
+
const probeKey = nullableText(candidate.probeKey);
|
|
149
|
+
const lastReportId = nullableText(candidate.lastReportId);
|
|
150
|
+
const retryKey = nullableText(candidate.retryKey);
|
|
151
|
+
if (probeKey === undefined || lastReportId === undefined || retryKey === undefined)
|
|
152
|
+
return undefined;
|
|
153
|
+
const waitLease = parseWaitLease(candidate.waitLease);
|
|
154
|
+
if (waitLease === undefined)
|
|
155
|
+
return undefined;
|
|
156
|
+
if (candidate.safetyPauseReason !== null &&
|
|
157
|
+
candidate.safetyPauseReason !== 'retryRecurrence' &&
|
|
158
|
+
candidate.safetyPauseReason !== 'invalidProbeReport')
|
|
159
|
+
return undefined;
|
|
160
|
+
return {
|
|
161
|
+
...base,
|
|
162
|
+
outcome: candidate.outcome,
|
|
163
|
+
unchangedContinuations,
|
|
164
|
+
probeKey,
|
|
165
|
+
lastReportId,
|
|
166
|
+
waitLease,
|
|
167
|
+
retryKey,
|
|
168
|
+
safetyPauseReason: candidate.safetyPauseReason,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function uniqueSupportedWakeConditions(conditions) {
|
|
172
|
+
return [...new Set(conditions)].filter((condition) => observableWakeConditions.includes(condition));
|
|
173
|
+
}
|
|
174
|
+
function isProtocolOutcome(value) {
|
|
175
|
+
return [
|
|
176
|
+
'active',
|
|
177
|
+
'probeRequired',
|
|
178
|
+
'parked',
|
|
179
|
+
'retrying',
|
|
180
|
+
'attentionRequired',
|
|
181
|
+
'safetyPaused',
|
|
182
|
+
].includes(value);
|
|
183
|
+
}
|
|
184
|
+
function nullableText(value) {
|
|
185
|
+
return value === null ? null : typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
186
|
+
}
|
|
187
|
+
function parseWaitLease(value) {
|
|
188
|
+
if (value === null)
|
|
189
|
+
return null;
|
|
190
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
191
|
+
return undefined;
|
|
192
|
+
const candidate = value;
|
|
193
|
+
if (typeof candidate.id !== 'string' ||
|
|
194
|
+
!candidate.id ||
|
|
195
|
+
typeof candidate.probeKey !== 'string' ||
|
|
196
|
+
!candidate.probeKey ||
|
|
197
|
+
!Array.isArray(candidate.wakeConditions))
|
|
198
|
+
return undefined;
|
|
199
|
+
const wakeConditions = uniqueSupportedWakeConditions(candidate.wakeConditions);
|
|
200
|
+
return wakeConditions.length === candidate.wakeConditions.length
|
|
201
|
+
? { id: candidate.id, probeKey: candidate.probeKey, wakeConditions }
|
|
202
|
+
: undefined;
|
|
203
|
+
}
|
|
@@ -11,7 +11,10 @@ export function isValidInteractionResponse(kind, value) {
|
|
|
11
11
|
return false;
|
|
12
12
|
if (kind === 'userInput')
|
|
13
13
|
return isValidUserInputResponse(value);
|
|
14
|
-
if (kind === 'quiz' ||
|
|
14
|
+
if (kind === 'quiz' ||
|
|
15
|
+
kind === 'orgPlanAttention' ||
|
|
16
|
+
kind === 'orgPlanCheckpoint' ||
|
|
17
|
+
kind === 'autopilotWaitLease')
|
|
15
18
|
return false;
|
|
16
19
|
if (kind === 'permissionsApproval')
|
|
17
20
|
return isRecord(value.permissions) && (value.scope === 'turn' || value.scope === 'session');
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { GESTALT_QUIZ_TOOL_NAME, parseQuiz } from '../../../shared/contracts/quiz.js';
|
|
7
7
|
import { GESTALT_ORG_PLAN_ATTENTION_TOOL_NAME, parseOrgPlanAttention, } from '../../../shared/contracts/org-plan-attention.js';
|
|
8
8
|
import { GESTALT_ORG_PLAN_CHECKPOINT_TOOL_NAME, parseOrgPlanCheckpoint, } from '../../../shared/contracts/org-plan-checkpoint.js';
|
|
9
|
+
import { GESTALT_AUTOPILOT_WAIT_LEASE_TOOL_NAME, parseAutopilotWaitLease, } from '../../../shared/contracts/autopilot-wait-lease.js';
|
|
9
10
|
export function toPendingInteraction(input) {
|
|
10
11
|
if (!Number.isSafeInteger(input.id) || !isRecord(input.params))
|
|
11
12
|
return null;
|
|
@@ -26,6 +27,12 @@ export function toPendingInteraction(input) {
|
|
|
26
27
|
? { requestId: String(input.id), kind: 'orgPlanCheckpoint', payload: checkpoint }
|
|
27
28
|
: null;
|
|
28
29
|
}
|
|
30
|
+
if (input.params.tool === GESTALT_AUTOPILOT_WAIT_LEASE_TOOL_NAME) {
|
|
31
|
+
const lease = parseAutopilotWaitLease(input.params.arguments);
|
|
32
|
+
return lease
|
|
33
|
+
? { requestId: String(input.id), kind: 'autopilotWaitLease', payload: lease }
|
|
34
|
+
: null;
|
|
35
|
+
}
|
|
29
36
|
return null;
|
|
30
37
|
}
|
|
31
38
|
const kind = {
|
|
@@ -45,6 +52,12 @@ export function resolvedServerRequestId(input) {
|
|
|
45
52
|
return null;
|
|
46
53
|
return String(requestId);
|
|
47
54
|
}
|
|
55
|
+
export function isAutopilotWaitLeaseCall(input) {
|
|
56
|
+
return (Number.isSafeInteger(input.id) &&
|
|
57
|
+
input.method === 'item/tool/call' &&
|
|
58
|
+
isRecord(input.params) &&
|
|
59
|
+
input.params.tool === GESTALT_AUTOPILOT_WAIT_LEASE_TOOL_NAME);
|
|
60
|
+
}
|
|
48
61
|
function isRecord(value) {
|
|
49
62
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
50
63
|
}
|
|
@@ -10,6 +10,7 @@ import { canRebindMissingRollout, rebindMissingRollout, } from '../../features/s
|
|
|
10
10
|
import { gestaltQuizDynamicTool } from '../../../shared/contracts/quiz.js';
|
|
11
11
|
import { gestaltOrgPlanAttentionDynamicTool } from '../../../shared/contracts/org-plan-attention.js';
|
|
12
12
|
import { gestaltOrgPlanCheckpointDynamicTool } from '../../../shared/contracts/org-plan-checkpoint.js';
|
|
13
|
+
import { gestaltAutopilotWaitLeaseDynamicTool } from '../../../shared/contracts/autopilot-wait-lease.js';
|
|
13
14
|
import { countDiffLines } from '../../../shared/contracts/file-change.js';
|
|
14
15
|
import { threadPlanName } from './thread-plan-name.js';
|
|
15
16
|
import { WriterAcquisitionError, } from '../../features/sessions/application/writer-acquisition.js';
|
|
@@ -568,6 +569,7 @@ export class CodexSessionRuntime {
|
|
|
568
569
|
gestaltQuizDynamicTool,
|
|
569
570
|
gestaltOrgPlanAttentionDynamicTool,
|
|
570
571
|
gestaltOrgPlanCheckpointDynamicTool,
|
|
572
|
+
gestaltAutopilotWaitLeaseDynamicTool,
|
|
571
573
|
],
|
|
572
574
|
});
|
|
573
575
|
result = {
|
|
@@ -658,6 +660,7 @@ export class CodexSessionRuntime {
|
|
|
658
660
|
gestaltQuizDynamicTool,
|
|
659
661
|
gestaltOrgPlanAttentionDynamicTool,
|
|
660
662
|
gestaltOrgPlanCheckpointDynamicTool,
|
|
663
|
+
gestaltAutopilotWaitLeaseDynamicTool,
|
|
661
664
|
],
|
|
662
665
|
...(session.model ? { model: session.model } : {}),
|
|
663
666
|
...(session.executionPolicy?.sandbox ? { sandbox: session.executionPolicy.sandbox } : {}),
|
|
@@ -16,7 +16,14 @@ export class SqliteAutopilotStore {
|
|
|
16
16
|
if (!row)
|
|
17
17
|
return null;
|
|
18
18
|
const lifecycle = parseLifecycle(row.lifecycle_json);
|
|
19
|
-
if (![
|
|
19
|
+
if (![
|
|
20
|
+
'disabled',
|
|
21
|
+
'monitoring',
|
|
22
|
+
'backoff',
|
|
23
|
+
'attentionRequired',
|
|
24
|
+
'completed',
|
|
25
|
+
'safetyPaused',
|
|
26
|
+
].includes(String(row.state)) ||
|
|
20
27
|
![
|
|
21
28
|
'manualDisabled',
|
|
22
29
|
'planRequired',
|
|
@@ -30,6 +37,7 @@ export class SqliteAutopilotStore {
|
|
|
30
37
|
'planRemoved',
|
|
31
38
|
'planReplaced',
|
|
32
39
|
'sessionEnded',
|
|
40
|
+
'safetyPaused',
|
|
33
41
|
'null',
|
|
34
42
|
].includes(String(row.stop_reason)) ||
|
|
35
43
|
!Number.isSafeInteger(Number(row.generation)) ||
|
|
@@ -57,10 +65,11 @@ export class SqliteAutopilotStore {
|
|
|
57
65
|
save(state) {
|
|
58
66
|
this.db
|
|
59
67
|
.prepare('INSERT INTO autopilot_sessions (session_id,state,requested_enabled,plan_identity,plan_fingerprint,generation,no_progress_count,next_evaluation_at,last_control_id,stop_reason,lifecycle_json,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(session_id) DO UPDATE SET state=excluded.state,requested_enabled=excluded.requested_enabled,plan_identity=excluded.plan_identity,plan_fingerprint=excluded.plan_fingerprint,generation=excluded.generation,no_progress_count=excluded.no_progress_count,next_evaluation_at=excluded.next_evaluation_at,last_control_id=excluded.last_control_id,stop_reason=excluded.stop_reason,lifecycle_json=excluded.lifecycle_json,updated_at=excluded.updated_at')
|
|
60
|
-
.run(state.sessionId, state.state, state.requestedEnabled ? 1 : 0, state.planIdentity, state.planFingerprint, state.generation, state.consecutiveNoProgress, state.nextEvaluationAt, state.lastControlId, state.stopReason, state.executor || state.blocking || state.checkpoints
|
|
68
|
+
.run(state.sessionId, state.state, state.requestedEnabled ? 1 : 0, state.planIdentity, state.planFingerprint, state.generation, state.consecutiveNoProgress, state.nextEvaluationAt, state.lastControlId, state.stopReason, state.executor || state.blocking || state.supervision || state.checkpoints
|
|
61
69
|
? JSON.stringify({
|
|
62
70
|
executor: state.executor,
|
|
63
71
|
blocking: state.blocking,
|
|
72
|
+
supervision: state.supervision,
|
|
64
73
|
checkpoints: state.checkpoints,
|
|
65
74
|
})
|
|
66
75
|
: null, state.updatedAt);
|
|
@@ -154,9 +154,11 @@ class ActiveLease {
|
|
|
154
154
|
onActiveStatusPath;
|
|
155
155
|
onClose;
|
|
156
156
|
watcher;
|
|
157
|
+
planWatcher;
|
|
157
158
|
debounce;
|
|
158
159
|
closed = false;
|
|
159
160
|
activeStatusPath;
|
|
161
|
+
activePlanPath;
|
|
160
162
|
lastEmittedUpdate;
|
|
161
163
|
constructor(statusDirectory, sessionId, workspacePath, listener, planReadFilesystem, isDismissed, onActiveStatusPath, onClose) {
|
|
162
164
|
this.statusDirectory = statusDirectory;
|
|
@@ -184,7 +186,10 @@ class ActiveLease {
|
|
|
184
186
|
this.closed = true;
|
|
185
187
|
if (this.debounce)
|
|
186
188
|
clearTimeout(this.debounce);
|
|
189
|
+
if (this.planDebounce)
|
|
190
|
+
clearTimeout(this.planDebounce);
|
|
187
191
|
void this.watcher?.return?.();
|
|
192
|
+
void this.planWatcher?.return?.();
|
|
188
193
|
this.onClose();
|
|
189
194
|
}
|
|
190
195
|
async remove() {
|
|
@@ -226,6 +231,35 @@ class ActiveLease {
|
|
|
226
231
|
void (pendingStatusPath ? this.refreshPath(pendingStatusPath) : this.refreshLatest());
|
|
227
232
|
}, 25);
|
|
228
233
|
}
|
|
234
|
+
watchActivePlan(planPath) {
|
|
235
|
+
if (this.activePlanPath === planPath && this.planWatcher)
|
|
236
|
+
return;
|
|
237
|
+
void this.planWatcher?.return?.();
|
|
238
|
+
this.activePlanPath = planPath;
|
|
239
|
+
void (async () => {
|
|
240
|
+
try {
|
|
241
|
+
this.planWatcher = watch(dirname(planPath), { persistent: false });
|
|
242
|
+
for await (const event of this.planWatcher) {
|
|
243
|
+
if (this.closed || basename(String(event.filename ?? '')) !== basename(planPath))
|
|
244
|
+
continue;
|
|
245
|
+
this.schedulePlanRefresh();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// The status signal remains the bounded recovery path.
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
}
|
|
253
|
+
planDebounce;
|
|
254
|
+
schedulePlanRefresh() {
|
|
255
|
+
if (this.planDebounce)
|
|
256
|
+
clearTimeout(this.planDebounce);
|
|
257
|
+
this.planDebounce = setTimeout(() => {
|
|
258
|
+
this.planDebounce = undefined;
|
|
259
|
+
if (this.activeStatusPath)
|
|
260
|
+
void this.refreshPath(this.activeStatusPath);
|
|
261
|
+
}, 25);
|
|
262
|
+
}
|
|
229
263
|
async refreshCurrent() {
|
|
230
264
|
return this.activeStatusPath ? this.refreshPath(this.activeStatusPath) : this.refreshLatest();
|
|
231
265
|
}
|
|
@@ -274,6 +308,7 @@ class ActiveLease {
|
|
|
274
308
|
if (!this.closed && !(await this.isDismissed(identity)) && !this.closed) {
|
|
275
309
|
const previousStatusPath = this.activeStatusPath;
|
|
276
310
|
this.activeStatusPath = statusPath;
|
|
311
|
+
this.watchActivePlan(planPath);
|
|
277
312
|
this.onActiveStatusPath(statusPath);
|
|
278
313
|
const update = {
|
|
279
314
|
kind: 'updated',
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
export const GESTALT_AUTOPILOT_WAIT_LEASE_TOOL_NAME = 'gestalt_autopilot_wait_lease';
|
|
7
|
+
export const autopilotWaitWakeConditions = [
|
|
8
|
+
'planChanged',
|
|
9
|
+
'reviewChanged',
|
|
10
|
+
'checkpointChanged',
|
|
11
|
+
'interactionChanged',
|
|
12
|
+
'executorChanged',
|
|
13
|
+
'processExited',
|
|
14
|
+
'processResultAvailable',
|
|
15
|
+
'processLimitBreached',
|
|
16
|
+
'agentActivityChanged',
|
|
17
|
+
];
|
|
18
|
+
export const gestaltAutopilotWaitLeaseDynamicTool = {
|
|
19
|
+
type: 'function',
|
|
20
|
+
name: GESTALT_AUTOPILOT_WAIT_LEASE_TOOL_NAME,
|
|
21
|
+
description: 'Register a bounded observable wait for the current supervised session after an Autopilot probe. This cannot request human authority and never accepts transcript prose.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
required: ['version', 'reportId', 'leaseId', 'wakeConditions'],
|
|
26
|
+
properties: {
|
|
27
|
+
version: { const: 1 },
|
|
28
|
+
reportId: { type: 'string', minLength: 1, maxLength: 128 },
|
|
29
|
+
leaseId: { type: 'string', minLength: 1, maxLength: 128 },
|
|
30
|
+
wakeConditions: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
minItems: 1,
|
|
33
|
+
maxItems: 4,
|
|
34
|
+
uniqueItems: true,
|
|
35
|
+
items: { type: 'string', enum: autopilotWaitWakeConditions },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export function parseAutopilotWaitLease(value) {
|
|
41
|
+
if (!record(value) || Object.keys(value).length !== 4 || value.version !== 1)
|
|
42
|
+
return null;
|
|
43
|
+
if (!bounded(value.reportId) || !bounded(value.leaseId) || !Array.isArray(value.wakeConditions))
|
|
44
|
+
return null;
|
|
45
|
+
const wakeConditions = value.wakeConditions;
|
|
46
|
+
if (wakeConditions.length < 1 ||
|
|
47
|
+
wakeConditions.length > 4 ||
|
|
48
|
+
new Set(wakeConditions).size !== wakeConditions.length ||
|
|
49
|
+
!wakeConditions.every((condition) => typeof condition === 'string' &&
|
|
50
|
+
autopilotWaitWakeConditions.includes(condition)))
|
|
51
|
+
return null;
|
|
52
|
+
return { version: 1, reportId: value.reportId, leaseId: value.leaseId, wakeConditions };
|
|
53
|
+
}
|
|
54
|
+
export function autopilotWaitLeaseToolResponse() {
|
|
55
|
+
return { success: true, contentItems: [{ type: 'inputText', text: '{"accepted":true}' }] };
|
|
56
|
+
}
|
|
57
|
+
function record(value) {
|
|
58
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
59
|
+
}
|
|
60
|
+
function bounded(value) {
|
|
61
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 128;
|
|
62
|
+
}
|