blun-king-cli 9.1.420 → 9.1.422
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/LIESMICH.txt +11 -0
- package/README.md +11 -0
- package/bin/cognitive-action-checkpoint.cjs +18 -5
- package/bin/cognitive-goal-autostart-policy.cjs +46 -5
- package/bin/cognitive-goal-time-trigger-controller.cjs +146 -0
- package/blun.mjs +81 -20
- package/package.json +1 -1
package/LIESMICH.txt
CHANGED
|
@@ -97,6 +97,17 @@ erzeugen dadurch keine leeren Folgezüge. Beim nächsten Ereignis wird der
|
|
|
97
97
|
gespeicherte Auslöser erneut eingeordnet. Sofortige Ziele und ältere Ziele ohne
|
|
98
98
|
Checkpoint laufen unverändert weiter.
|
|
99
99
|
|
|
100
|
+
Ab BLUN King 9.1.421 muss ein wartendes Ziel mit Zeit-Trigger einen genauen
|
|
101
|
+
`dueAt`-Zeitpunkt speichern. Der Zeitpunkt wird auf UTC normalisiert. Bei einem
|
|
102
|
+
natürlichen Sitzungsstart im Auto- oder God-Modus wartet das Ziel vor diesem
|
|
103
|
+
Zeitpunkt weiter und startet, sobald der Zeitpunkt erreicht oder überschritten
|
|
104
|
+
ist. Der erste Fortsetzungszug erhält die gespeicherte Fälligkeit als
|
|
105
|
+
ausdrücklichen Trigger-Beleg. Andere Trigger-Arten dürfen kein `dueAt`
|
|
106
|
+
enthalten. Ein Laufzeit-Wecker für eine durchgehend geöffnete Sitzung ist in
|
|
107
|
+
diesem Release noch nicht enthalten.
|
|
108
|
+
|
|
109
|
+
Ab BLUN King 9.1.422 überwacht auch eine durchgehend geöffnete, untätige Sitzung ihren gespeicherten Zeit-Trigger. Sobald `dueAt` erreicht oder überschritten ist, fügt der Auto- oder God-Modus genau eine verborgene Fortsetzung in dieselbe Sitzung ein. Wird der Zeitpunkt während einer laufenden Antwort, Verdichtung, wartenden Nachricht, eines Befehls oder Dialogs fällig, wartet die Fortsetzung bis zum Leerlauf. Unmittelbar vor dem Start liest King Ziel, Checkpoint-Revision, Fälligkeit, Berechtigungsmodus und Sitzung erneut; ein geänderter oder veralteter Trigger kann deshalb nicht auslösen. Stoppen, Notausgang, Entladen und Wechseln der Sitzung entsorgen den Timer. Im manuellen Modus erfolgt kein selbstständiger Start.
|
|
110
|
+
|
|
100
111
|
Zuverlässiger King-Start
|
|
101
112
|
-----------------------
|
|
102
113
|
Bei einer ausdrücklich als wiederholbar gekennzeichneten Serverüberlastung
|
package/README.md
CHANGED
|
@@ -116,6 +116,17 @@ erzeugen dadurch keine leeren Folgezüge. Beim nächsten Ereignis wird der
|
|
|
116
116
|
gespeicherte Auslöser erneut eingeordnet. Sofortige Ziele und ältere Ziele ohne
|
|
117
117
|
Checkpoint laufen unverändert weiter.
|
|
118
118
|
|
|
119
|
+
Ab BLUN King 9.1.421 muss ein wartendes Ziel mit Zeit-Trigger einen genauen
|
|
120
|
+
`dueAt`-Zeitpunkt speichern. Der Zeitpunkt wird auf UTC normalisiert. Bei einem
|
|
121
|
+
natürlichen Sitzungsstart im Auto- oder God-Modus wartet das Ziel vor diesem
|
|
122
|
+
Zeitpunkt weiter und startet, sobald der Zeitpunkt erreicht oder überschritten
|
|
123
|
+
ist. Der erste Fortsetzungszug erhält die gespeicherte Fälligkeit als
|
|
124
|
+
ausdrücklichen Trigger-Beleg. Andere Trigger-Arten dürfen kein `dueAt`
|
|
125
|
+
enthalten. Ein Laufzeit-Wecker für eine durchgehend geöffnete Sitzung ist in
|
|
126
|
+
diesem Release noch nicht enthalten.
|
|
127
|
+
|
|
128
|
+
Ab BLUN King 9.1.422 überwacht auch eine durchgehend geöffnete, untätige Sitzung ihren gespeicherten Zeit-Trigger. Sobald `dueAt` erreicht oder überschritten ist, fügt der Auto- oder God-Modus genau eine verborgene Fortsetzung in dieselbe Sitzung ein. Wird der Zeitpunkt während einer laufenden Antwort, Verdichtung, wartenden Nachricht, eines Befehls oder Dialogs fällig, wartet die Fortsetzung bis zum Leerlauf. Unmittelbar vor dem Start liest King Ziel, Checkpoint-Revision, Fälligkeit, Berechtigungsmodus und Sitzung erneut; ein geänderter oder veralteter Trigger kann deshalb nicht auslösen. Stoppen, Notausgang, Entladen und Wechseln der Sitzung entsorgen den Timer. Im manuellen Modus erfolgt kein selbstständiger Start.
|
|
129
|
+
|
|
119
130
|
## Zuverlässiger King-Start
|
|
120
131
|
|
|
121
132
|
Bei einer vom Server ausdrücklich als wiederholbar gekennzeichneten
|
|
@@ -20,11 +20,12 @@ const PROBLEM_FRAME_KEYS = new Set([
|
|
|
20
20
|
'successCriterion', 'missingKnowledge', 'candidateActions', 'selectedAction',
|
|
21
21
|
'selectionReason', 'supportChoice', 'risk', 'reversibility',
|
|
22
22
|
]);
|
|
23
|
-
const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition']);
|
|
23
|
+
const NEXT_TRIGGER_KEYS = new Set(['kind', 'condition', 'dueAt']);
|
|
24
24
|
const EVIDENCE_INPUT_KEYS = new Set([
|
|
25
25
|
'turnId', 'toolCallId', 'toolName', 'decision', 'outcome', 'durationMs',
|
|
26
26
|
]);
|
|
27
27
|
const EVIDENCE_DIGEST_RE = /^[a-f0-9]{16}$/u;
|
|
28
|
+
const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u;
|
|
28
29
|
|
|
29
30
|
function bounded(value, field, max = 512) {
|
|
30
31
|
const text = String(value ?? '')
|
|
@@ -35,9 +36,12 @@ function bounded(value, field, max = 512) {
|
|
|
35
36
|
return text.length <= max ? text : `${text.slice(0, max - 3).trimEnd()}...`;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
function normalizedTimestamp(value) {
|
|
39
|
+
function normalizedTimestamp(value, field = 'updatedAt', requireIsoString = false) {
|
|
40
|
+
if (requireIsoString && (typeof value !== 'string' || !ISO_TIMESTAMP_RE.test(value))) {
|
|
41
|
+
throw new TypeError(`${field} must be an ISO timestamp`);
|
|
42
|
+
}
|
|
39
43
|
const date = new Date(value);
|
|
40
|
-
if (!Number.isFinite(date.getTime())) throw new TypeError(
|
|
44
|
+
if (!Number.isFinite(date.getTime())) throw new TypeError(`${field} must be an ISO timestamp`);
|
|
41
45
|
return date.toISOString();
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -96,10 +100,18 @@ function normalizeNextTrigger(input, phase) {
|
|
|
96
100
|
if (phase !== 'wait' && kind !== 'immediate') {
|
|
97
101
|
throw new TypeError('non-wait phase requires an immediate nextTrigger');
|
|
98
102
|
}
|
|
99
|
-
|
|
103
|
+
if (kind === 'time' && input.dueAt === undefined) {
|
|
104
|
+
throw new TypeError('time nextTrigger requires dueAt');
|
|
105
|
+
}
|
|
106
|
+
if (kind !== 'time' && input.dueAt !== undefined) {
|
|
107
|
+
throw new TypeError('dueAt is only valid for a time nextTrigger');
|
|
108
|
+
}
|
|
109
|
+
const trigger = {
|
|
100
110
|
kind,
|
|
101
111
|
condition: bounded(input.condition, 'nextTrigger condition'),
|
|
102
|
-
}
|
|
112
|
+
};
|
|
113
|
+
if (kind === 'time') trigger.dueAt = normalizedTimestamp(input.dueAt, 'nextTrigger dueAt', true);
|
|
114
|
+
return Object.freeze(trigger);
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
function normalizedEvidenceBasis(value, allowLegacy = false) {
|
|
@@ -287,6 +299,7 @@ function projectActionCheckpoint(checkpoint) {
|
|
|
287
299
|
lines.push(`Expected evidence: ${value.expectedEvidence}`);
|
|
288
300
|
if (value.nextTrigger !== undefined) {
|
|
289
301
|
lines.push(`Next trigger: ${value.nextTrigger.kind.replaceAll('_', ' ')} - ${value.nextTrigger.condition}`);
|
|
302
|
+
if (value.nextTrigger.dueAt !== undefined) lines.push(`Due at: ${value.nextTrigger.dueAt}`);
|
|
290
303
|
}
|
|
291
304
|
if (value.problemFrame !== undefined) {
|
|
292
305
|
const frame = value.problemFrame;
|
|
@@ -2,23 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
const AUTONOMOUS_PERMISSION_MODES = new Set(['auto', 'yolo']);
|
|
4
4
|
const ACTIVE_PHASES = new Set(['orient', 'plan', 'act', 'verify', 'learn']);
|
|
5
|
+
const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u;
|
|
5
6
|
const START = Object.freeze({ kind: 'start', trigger: 'immediate' });
|
|
6
7
|
const WAIT = Object.freeze({ kind: 'wait' });
|
|
7
8
|
const CONTINUE = Object.freeze({ kind: 'continue' });
|
|
8
9
|
const YIELD = Object.freeze({ kind: 'yield' });
|
|
10
|
+
const IGNORE = Object.freeze({ kind: 'ignore' });
|
|
9
11
|
|
|
10
|
-
function
|
|
12
|
+
function goalTimeTriggerDecision({ goal, permissionMode, now = new Date() } = {}) {
|
|
13
|
+
if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return IGNORE;
|
|
14
|
+
if (goal.status !== 'active' || !AUTONOMOUS_PERMISSION_MODES.has(permissionMode)) return IGNORE;
|
|
15
|
+
if (typeof goal.goalId !== 'string' || goal.goalId.trim().length === 0) return IGNORE;
|
|
16
|
+
|
|
17
|
+
const checkpoint = goal.actionCheckpoint;
|
|
18
|
+
if (!checkpoint || typeof checkpoint !== 'object' || Array.isArray(checkpoint)) return IGNORE;
|
|
19
|
+
if (!Number.isInteger(checkpoint.revision) || checkpoint.revision < 1) return IGNORE;
|
|
20
|
+
if (checkpoint.phase !== 'wait') return IGNORE;
|
|
21
|
+
const trigger = checkpoint.nextTrigger;
|
|
22
|
+
if (!trigger || typeof trigger !== 'object' || Array.isArray(trigger)) return IGNORE;
|
|
23
|
+
if (trigger.kind !== 'time' || typeof trigger.condition !== 'string' || trigger.condition.trim().length === 0) return IGNORE;
|
|
24
|
+
|
|
25
|
+
const dueAt = String(trigger.dueAt ?? '');
|
|
26
|
+
if (!ISO_TIMESTAMP_RE.test(dueAt)) return IGNORE;
|
|
27
|
+
const dueAtMs = new Date(dueAt).getTime();
|
|
28
|
+
const nowMs = new Date(now).getTime();
|
|
29
|
+
if (!Number.isFinite(dueAtMs) || !Number.isFinite(nowMs)) return IGNORE;
|
|
30
|
+
const key = `${goal.goalId}:${checkpoint.revision}:${dueAt}`;
|
|
31
|
+
if (dueAtMs > nowMs) return Object.freeze({
|
|
32
|
+
kind: 'schedule',
|
|
33
|
+
key,
|
|
34
|
+
dueAt,
|
|
35
|
+
delayMs: dueAtMs - nowMs,
|
|
36
|
+
});
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
kind: 'due',
|
|
39
|
+
key,
|
|
40
|
+
dueAt,
|
|
41
|
+
observation: `The checkpointed time trigger became due at ${dueAt}.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function goalAutostartDecision({ goal, permissionMode, now = new Date() } = {}) {
|
|
11
46
|
if (!goal || typeof goal !== 'object' || Array.isArray(goal)) return WAIT;
|
|
12
47
|
if (goal.status !== 'active' || !AUTONOMOUS_PERMISSION_MODES.has(permissionMode)) return WAIT;
|
|
13
48
|
|
|
14
49
|
const checkpoint = goal.actionCheckpoint;
|
|
15
50
|
if (!checkpoint || typeof checkpoint !== 'object' || Array.isArray(checkpoint)) return WAIT;
|
|
16
|
-
if (!ACTIVE_PHASES.has(checkpoint.phase)) return WAIT;
|
|
17
51
|
const trigger = checkpoint.nextTrigger;
|
|
18
52
|
if (!trigger || typeof trigger !== 'object' || Array.isArray(trigger)) return WAIT;
|
|
19
|
-
if (trigger.kind !== 'immediate') return WAIT;
|
|
20
53
|
if (typeof trigger.condition !== 'string' || trigger.condition.trim().length === 0) return WAIT;
|
|
21
|
-
return START;
|
|
54
|
+
if (ACTIVE_PHASES.has(checkpoint.phase) && trigger.kind === 'immediate') return START;
|
|
55
|
+
const timed = goalTimeTriggerDecision({ goal, permissionMode, now });
|
|
56
|
+
if (timed.kind !== 'due') return WAIT;
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
kind: 'start',
|
|
59
|
+
trigger: 'time',
|
|
60
|
+
dueAt: timed.dueAt,
|
|
61
|
+
observation: timed.observation,
|
|
62
|
+
});
|
|
22
63
|
}
|
|
23
64
|
|
|
24
65
|
function goalContinuationDecision({ goal } = {}) {
|
|
@@ -28,4 +69,4 @@ function goalContinuationDecision({ goal } = {}) {
|
|
|
28
69
|
return checkpoint.phase === 'wait' ? YIELD : CONTINUE;
|
|
29
70
|
}
|
|
30
71
|
|
|
31
|
-
module.exports = { goalAutostartDecision, goalContinuationDecision };
|
|
72
|
+
module.exports = { goalAutostartDecision, goalContinuationDecision, goalTimeTriggerDecision };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { goalTimeTriggerDecision } = require('./cognitive-goal-autostart-policy.cjs');
|
|
4
|
+
|
|
5
|
+
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
6
|
+
|
|
7
|
+
class GoalTimeTriggerController {
|
|
8
|
+
#readState;
|
|
9
|
+
#start;
|
|
10
|
+
#now;
|
|
11
|
+
#setTimer;
|
|
12
|
+
#clearTimer;
|
|
13
|
+
#onError;
|
|
14
|
+
#retryDelayMs;
|
|
15
|
+
#timer;
|
|
16
|
+
#timerKey;
|
|
17
|
+
#inFlightKey;
|
|
18
|
+
#startedKey;
|
|
19
|
+
#generation = 0;
|
|
20
|
+
#disposed = false;
|
|
21
|
+
|
|
22
|
+
constructor({
|
|
23
|
+
readState,
|
|
24
|
+
start,
|
|
25
|
+
now = () => new Date(),
|
|
26
|
+
setTimer = setTimeout,
|
|
27
|
+
clearTimer = clearTimeout,
|
|
28
|
+
onError = () => {},
|
|
29
|
+
retryDelayMs = 1_000,
|
|
30
|
+
} = {}) {
|
|
31
|
+
if (typeof readState !== 'function') throw new TypeError('readState must be a function');
|
|
32
|
+
if (typeof start !== 'function') throw new TypeError('start must be a function');
|
|
33
|
+
this.#readState = readState;
|
|
34
|
+
this.#start = start;
|
|
35
|
+
this.#now = now;
|
|
36
|
+
this.#setTimer = setTimer;
|
|
37
|
+
this.#clearTimer = clearTimer;
|
|
38
|
+
this.#onError = onError;
|
|
39
|
+
this.#retryDelayMs = retryDelayMs;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sync() {
|
|
43
|
+
if (this.#disposed) return undefined;
|
|
44
|
+
const state = this.#readState() ?? {};
|
|
45
|
+
const decision = goalTimeTriggerDecision({
|
|
46
|
+
goal: state.goal,
|
|
47
|
+
permissionMode: state.permissionMode,
|
|
48
|
+
now: this.#now(),
|
|
49
|
+
});
|
|
50
|
+
if (decision.kind === 'ignore' || typeof state.sessionId !== 'string' || state.sessionId.length === 0) {
|
|
51
|
+
this.#clearScheduledTimer();
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const key = `${state.sessionId}:${decision.key}`;
|
|
56
|
+
if (this.#startedKey === key || this.#inFlightKey === key) {
|
|
57
|
+
this.#clearScheduledTimer();
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
if (decision.kind === 'schedule') {
|
|
61
|
+
this.#schedule(key, Math.min(MAX_TIMER_DELAY_MS, Math.max(0, decision.delayMs)));
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.#clearScheduledTimer();
|
|
66
|
+
if (state.idle !== true) return undefined;
|
|
67
|
+
return this.#startDueTrigger(key, decision);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
reset() {
|
|
71
|
+
this.#generation += 1;
|
|
72
|
+
this.#clearScheduledTimer();
|
|
73
|
+
this.#inFlightKey = undefined;
|
|
74
|
+
this.#startedKey = undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
dispose() {
|
|
78
|
+
this.reset();
|
|
79
|
+
this.#disposed = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#schedule(key, delayMs) {
|
|
83
|
+
if (this.#timer !== undefined && this.#timerKey === key) return;
|
|
84
|
+
this.#clearScheduledTimer();
|
|
85
|
+
this.#timerKey = key;
|
|
86
|
+
this.#timer = this.#setTimer(() => {
|
|
87
|
+
this.#timer = undefined;
|
|
88
|
+
this.#timerKey = undefined;
|
|
89
|
+
void this.sync();
|
|
90
|
+
}, delayMs);
|
|
91
|
+
this.#timer?.unref?.();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#startDueTrigger(key, decision) {
|
|
95
|
+
const generation = this.#generation;
|
|
96
|
+
this.#inFlightKey = key;
|
|
97
|
+
const trigger = {
|
|
98
|
+
key,
|
|
99
|
+
trigger: 'time',
|
|
100
|
+
dueAt: decision.dueAt,
|
|
101
|
+
observation: decision.observation,
|
|
102
|
+
};
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = this.#start(trigger);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
result = Promise.reject(error);
|
|
108
|
+
}
|
|
109
|
+
return Promise.resolve(result).then((accepted) => {
|
|
110
|
+
if (this.#disposed || generation !== this.#generation || this.#inFlightKey !== key) return false;
|
|
111
|
+
this.#inFlightKey = undefined;
|
|
112
|
+
if (accepted === true) {
|
|
113
|
+
this.#startedKey = key;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
this.#scheduleRetry(key);
|
|
117
|
+
return false;
|
|
118
|
+
}, (error) => {
|
|
119
|
+
if (!this.#disposed && generation === this.#generation && this.#inFlightKey === key) {
|
|
120
|
+
this.#inFlightKey = undefined;
|
|
121
|
+
this.#onError(error);
|
|
122
|
+
this.#scheduleRetry(key);
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#scheduleRetry(key) {
|
|
129
|
+
const state = this.#readState() ?? {};
|
|
130
|
+
const decision = goalTimeTriggerDecision({
|
|
131
|
+
goal: state.goal,
|
|
132
|
+
permissionMode: state.permissionMode,
|
|
133
|
+
now: this.#now(),
|
|
134
|
+
});
|
|
135
|
+
if (decision.kind !== 'due' || `${state.sessionId}:${decision.key}` !== key) return;
|
|
136
|
+
this.#schedule(key, this.#retryDelayMs);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#clearScheduledTimer() {
|
|
140
|
+
if (this.#timer !== undefined) this.#clearTimer(this.#timer);
|
|
141
|
+
this.#timer = undefined;
|
|
142
|
+
this.#timerKey = undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { GoalTimeTriggerController, MAX_TIMER_DELAY_MS };
|
package/blun.mjs
CHANGED
|
@@ -7,6 +7,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
7
7
|
import { createRequire } from "node:module";
|
|
8
8
|
import telegramConsoleStatusPolicy from "./bin/telegram-console-status-policy.cjs";
|
|
9
9
|
import cognitiveGoalAutostartPolicy from "./bin/cognitive-goal-autostart-policy.cjs";
|
|
10
|
+
import cognitiveGoalTimeTriggerController from "./bin/cognitive-goal-time-trigger-controller.cjs";
|
|
10
11
|
import crypto$1, { createHash, randomBytes, randomInt, randomUUID, timingSafeEqual } from "node:crypto";
|
|
11
12
|
import * as fs$16 from "node:fs";
|
|
12
13
|
import Kt, { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, promises, readFileSync, readSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
@@ -38,6 +39,7 @@ import { createServer } from "node:http";
|
|
|
38
39
|
import { pipeline as pipeline$1 } from "node:stream/promises";
|
|
39
40
|
const { writeTelegramConsoleStatus } = telegramConsoleStatusPolicy;
|
|
40
41
|
const { goalAutostartDecision, goalContinuationDecision } = cognitiveGoalAutostartPolicy;
|
|
42
|
+
const { GoalTimeTriggerController } = cognitiveGoalTimeTriggerController;
|
|
41
43
|
import { EventEmitter as EventEmitter$1 } from "node:events";
|
|
42
44
|
import { StringDecoder } from "node:string_decoder";
|
|
43
45
|
import co from "node:assert";
|
|
@@ -245769,7 +245771,8 @@ var init_events$1 = __esmMin((() => {
|
|
|
245769
245771
|
expectedEvidence: string(),
|
|
245770
245772
|
nextTrigger: object({
|
|
245771
245773
|
kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
|
|
245772
|
-
condition: string()
|
|
245774
|
+
condition: string(),
|
|
245775
|
+
dueAt: string().optional()
|
|
245773
245776
|
}).strict().optional(),
|
|
245774
245777
|
problemFrame: object({
|
|
245775
245778
|
successCriterion: string(),
|
|
@@ -260288,8 +260291,26 @@ function createActionCheckpointInputSchema(problemFrameSchema, requireProblemFra
|
|
|
260288
260291
|
expectedEvidence: string().min(1).max(512),
|
|
260289
260292
|
nextTrigger: object({
|
|
260290
260293
|
kind: _enum(["immediate", "external_event", "time", "dependency", "user_decision"]),
|
|
260291
|
-
condition: string().min(1).max(512)
|
|
260292
|
-
|
|
260294
|
+
condition: string().min(1).max(512),
|
|
260295
|
+
dueAt: string().min(1).max(64).optional()
|
|
260296
|
+
}).strict().superRefine((value, ctx) => {
|
|
260297
|
+
if (value.kind === "time") {
|
|
260298
|
+
if (value.dueAt === void 0) ctx.addIssue({
|
|
260299
|
+
code: "custom",
|
|
260300
|
+
path: ["dueAt"],
|
|
260301
|
+
message: "time nextTrigger requires dueAt"
|
|
260302
|
+
});
|
|
260303
|
+
else if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}(?::?\d{2})?)$/u.test(value.dueAt) || !Number.isFinite(new Date(value.dueAt).getTime())) ctx.addIssue({
|
|
260304
|
+
code: "custom",
|
|
260305
|
+
path: ["dueAt"],
|
|
260306
|
+
message: "dueAt must be an ISO timestamp"
|
|
260307
|
+
});
|
|
260308
|
+
} else if (value.dueAt !== void 0) ctx.addIssue({
|
|
260309
|
+
code: "custom",
|
|
260310
|
+
path: ["dueAt"],
|
|
260311
|
+
message: "dueAt is only valid for a time nextTrigger"
|
|
260312
|
+
});
|
|
260313
|
+
}),
|
|
260293
260314
|
problemFrame: requireProblemFrame ? problemFrameSchema : problemFrameSchema.optional()
|
|
260294
260315
|
}).strict();
|
|
260295
260316
|
}
|
|
@@ -262782,7 +262803,7 @@ var init_outcome_prompts = __esmMin((() => {}));
|
|
|
262782
262803
|
//#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.md?raw
|
|
262783
262804
|
var update_goal_default;
|
|
262784
262805
|
var init_update_goal$1 = __esmMin((() => {
|
|
262785
|
-
update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
|
|
262806
|
+
update_goal_default = "Update the current autonomous goal. Set `status` only for a lifecycle change. After a coherent work slice, save `actionCheckpoint` with a monotone revision, the last verified result, exact next action, expected evidence, exact `nextTrigger`, and an explicit evidence basis. Persist the exact `nextTrigger` that releases `nextAction`: use `immediate` outside the `wait` phase; while waiting, name the external event, time, dependency, or user decision instead of pretending work can continue. A `time` trigger must include the exact ISO timestamp in `dueAt`; no other trigger kind may include `dueAt`. Use `runtime_tool` only when a successful tool in this turn measured the result; use `user_statement` for a direct user assertion, `external_report` for a report not independently measured here, and `carried_forward` only when the last verified text is unchanged. Classify knowledge as `verified`, `credible_unverified`, `hypothesis`, `uncertain_memory`, `stale`, or `unknown`; never present a weaker state as verified, and preserve the state on carry-forward. Start at revision 1 and increment the currently projected revision by exactly one; stale writers fail closed. This is durable progress state, not permission, and should change only when the facts change. A checkpoint-only call keeps the goal active.\n\n- `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal.\n- `complete` — the objective is fully satisfied, all files are written, all tests pass, and any stated validation has passed. When the goal has a completion criterion, first save a `verify` checkpoint with `runtime_tool`, `verified`, and a successful runtime evidence receipt.\n- `blocked` — a genuine external condition or required user decision prevents progress.\n- `paused` — set the goal aside for now.\n\nDo not mark complete after a plan or partial result. If useful work remains, checkpoint it and continue. Do not ask for permission merely to execute an already authorized checkpoint; ask only at a real rights boundary or missing user decision.\n";
|
|
262786
262807
|
update_goal_default += "\nFor a non-trivial or unfamiliar problem, preserve `problemFrame` with the success criterion, missing knowledge, bounded candidate actions, selected action and reason, support choice, risk, and reversibility. The selected action must match one candidate. Problem framing is descriptive state and never grants permission.\n";
|
|
262787
262808
|
}));
|
|
262788
262809
|
//#endregion
|
|
@@ -507767,6 +507788,7 @@ var SessionEventHandler = class {
|
|
|
507767
507788
|
this.handleEvent(event, sendQueued);
|
|
507768
507789
|
});
|
|
507769
507790
|
this.syncMcpServerStatusSnapshot(session);
|
|
507791
|
+
this.host.syncGoalTimeTrigger();
|
|
507770
507792
|
}
|
|
507771
507793
|
async syncMcpServerStatusSnapshot(session) {
|
|
507772
507794
|
const { host } = this;
|
|
@@ -508270,6 +508292,7 @@ var SessionEventHandler = class {
|
|
|
508270
508292
|
}
|
|
508271
508293
|
handleGoalUpdated(event) {
|
|
508272
508294
|
this.host.setAppState({ goal: event.snapshot });
|
|
508295
|
+
this.host.syncGoalTimeTrigger();
|
|
508273
508296
|
if (event.snapshot === null && this.goalCompletionAwaitingClear) {
|
|
508274
508297
|
this.goalCompletionAwaitingClear = false;
|
|
508275
508298
|
this.queuedGoalPromotionPending = true;
|
|
@@ -516141,6 +516164,7 @@ var BlunTUI = class {
|
|
|
516141
516164
|
personalMemoryController;
|
|
516142
516165
|
customerMistakeController;
|
|
516143
516166
|
managedQuotaWarningController;
|
|
516167
|
+
goalTimeTriggerController;
|
|
516144
516168
|
managedQuotaWarningPersistence = Promise.resolve();
|
|
516145
516169
|
footerMounted = false;
|
|
516146
516170
|
/** Timer that auto-clears the one-shot "moved to background" footer hint. */
|
|
@@ -516244,6 +516268,13 @@ var BlunTUI = class {
|
|
|
516244
516268
|
}
|
|
516245
516269
|
}));
|
|
516246
516270
|
this.streamingUI = new StreamingUIController(this);
|
|
516271
|
+
this.goalTimeTriggerController = new GoalTimeTriggerController({
|
|
516272
|
+
readState: () => this.readGoalTimeTriggerState(),
|
|
516273
|
+
start: (trigger) => this.startAutonomousGoalContinuation(trigger),
|
|
516274
|
+
onError: (error) => {
|
|
516275
|
+
this.track("goal_time_trigger_failed", { error_type: error?.code ?? error?.name ?? "Error" });
|
|
516276
|
+
}
|
|
516277
|
+
});
|
|
516247
516278
|
this.authFlow = new AuthFlowController(this);
|
|
516248
516279
|
this.personalMemoryController = new PersonalMemoryController(this);
|
|
516249
516280
|
this.customerMistakeController = new CustomerMistakeHostController();
|
|
@@ -516666,6 +516697,41 @@ var BlunTUI = class {
|
|
|
516666
516697
|
});
|
|
516667
516698
|
return result.kind === "selected" ? result.path : void 0;
|
|
516668
516699
|
}
|
|
516700
|
+
readGoalTimeTriggerState() {
|
|
516701
|
+
return {
|
|
516702
|
+
sessionId: this.session?.id ?? "",
|
|
516703
|
+
goal: this.state.appState.goal,
|
|
516704
|
+
permissionMode: this.state.appState.permissionMode,
|
|
516705
|
+
idle: this.session !== void 0 && !this.isShuttingDown && !this.aborted && !this.queueCommandRunning && this.queueSteerInFlight === void 0 && !this.editorReplacementActive && !this.deferUserMessages && !this.streamingUI.hasActiveTurn() && this.state.appState.streamingPhase === "idle" && !this.state.appState.isCompacting && this.state.queuedMessages.length === 0
|
|
516706
|
+
};
|
|
516707
|
+
}
|
|
516708
|
+
syncGoalTimeTrigger() {
|
|
516709
|
+
if (this.isShuttingDown) return;
|
|
516710
|
+
void this.goalTimeTriggerController?.sync();
|
|
516711
|
+
}
|
|
516712
|
+
async startAutonomousGoalContinuation(autostart) {
|
|
516713
|
+
const session = this.session;
|
|
516714
|
+
if (session === void 0) return false;
|
|
516715
|
+
const sessionId = session.id;
|
|
516716
|
+
const autostartPrompt = autostart.observation === void 0 ? GOAL_CONTINUATION_PROMPT : `${GOAL_CONTINUATION_PROMPT}\n\n${autostart.observation}`;
|
|
516717
|
+
this.beginSessionRequest();
|
|
516718
|
+
this.setAppState({
|
|
516719
|
+
model: BLUN_KING_MODEL_ALIAS,
|
|
516720
|
+
modelFallbackAllowed: false
|
|
516721
|
+
});
|
|
516722
|
+
try {
|
|
516723
|
+
const result = await session.promptAccepted(autostartPrompt);
|
|
516724
|
+
if (!result.accepted && this.session?.id === sessionId) {
|
|
516725
|
+
this.setAppState({ streamingPhase: "idle" });
|
|
516726
|
+
this.resetLivePane();
|
|
516727
|
+
this.track("goal_autostart", { outcome: "not_accepted" });
|
|
516728
|
+
}
|
|
516729
|
+
return result.accepted === true;
|
|
516730
|
+
} catch (error) {
|
|
516731
|
+
if (this.session?.id === sessionId) this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
|
|
516732
|
+
return false;
|
|
516733
|
+
}
|
|
516734
|
+
}
|
|
516669
516735
|
async promptStartupResumeGoalIfNeeded() {
|
|
516670
516736
|
const session = this.session;
|
|
516671
516737
|
const goal = this.state.appState.goal;
|
|
@@ -516673,27 +516739,16 @@ var BlunTUI = class {
|
|
|
516673
516739
|
const sessionId = session.id;
|
|
516674
516740
|
const autostart = goalAutostartDecision({
|
|
516675
516741
|
goal,
|
|
516676
|
-
permissionMode: this.state.appState.permissionMode
|
|
516742
|
+
permissionMode: this.state.appState.permissionMode,
|
|
516743
|
+
now: new Date()
|
|
516677
516744
|
});
|
|
516678
516745
|
if (autostart.kind === "start") {
|
|
516679
516746
|
this.startupGoalPromptedSessionId = sessionId;
|
|
516680
|
-
this.
|
|
516681
|
-
this.
|
|
516682
|
-
model: BLUN_KING_MODEL_ALIAS,
|
|
516683
|
-
modelFallbackAllowed: false
|
|
516684
|
-
});
|
|
516685
|
-
try {
|
|
516686
|
-
const result = await session.promptAccepted(GOAL_CONTINUATION_PROMPT);
|
|
516687
|
-
if (!result.accepted && this.session?.id === sessionId) {
|
|
516688
|
-
this.setAppState({ streamingPhase: "idle" });
|
|
516689
|
-
this.resetLivePane();
|
|
516690
|
-
this.track("goal_autostart", { outcome: "not_accepted" });
|
|
516691
|
-
}
|
|
516692
|
-
} catch (error) {
|
|
516693
|
-
if (this.session?.id === sessionId) this.failSessionRequest(uiText("blunTui.session.sendFailed", { error: formatErrorMessage$2(error) }));
|
|
516694
|
-
}
|
|
516747
|
+
if (autostart.trigger === "time") await this.goalTimeTriggerController.sync();
|
|
516748
|
+
else await this.startAutonomousGoalContinuation(autostart);
|
|
516695
516749
|
return;
|
|
516696
516750
|
}
|
|
516751
|
+
this.syncGoalTimeTrigger();
|
|
516697
516752
|
if (goal.status !== "paused" && goal.status !== "blocked") return;
|
|
516698
516753
|
this.startupGoalPromptedSessionId = sessionId;
|
|
516699
516754
|
const choice = await promptStartupResumeGoal(this, goal.objective, startupResumeGoalPromptCopy());
|
|
@@ -516707,6 +516762,7 @@ var BlunTUI = class {
|
|
|
516707
516762
|
async stop(exitCode) {
|
|
516708
516763
|
if (this.isShuttingDown) return;
|
|
516709
516764
|
this.isShuttingDown = true;
|
|
516765
|
+
this.goalTimeTriggerController.dispose();
|
|
516710
516766
|
await this.flushInputDraft();
|
|
516711
516767
|
this.unregisterSignalHandlers();
|
|
516712
516768
|
this.aborted = true;
|
|
@@ -516786,6 +516842,7 @@ var BlunTUI = class {
|
|
|
516786
516842
|
}
|
|
516787
516843
|
emergencyTerminalExit(exitCode = 129) {
|
|
516788
516844
|
this.isShuttingDown = true;
|
|
516845
|
+
this.goalTimeTriggerController.dispose();
|
|
516789
516846
|
this.unregisterSignalHandlers();
|
|
516790
516847
|
this.channelQueueDeadline?.dispose();
|
|
516791
516848
|
this.directFocusController?.dispose();
|
|
@@ -518133,6 +518190,7 @@ var BlunTUI = class {
|
|
|
518133
518190
|
if (!hasPatchChanges(this.state.appState, effectivePatch)) return;
|
|
518134
518191
|
const additionalDirsChanged = "additionalDirs" in effectivePatch && !sameStringArrays(this.state.appState.additionalDirs, effectivePatch.additionalDirs ?? []);
|
|
518135
518192
|
const busyChanged = "streamingPhase" in effectivePatch || "isCompacting" in effectivePatch;
|
|
518193
|
+
const goalTimeTriggerChanged = busyChanged || "permissionMode" in effectivePatch;
|
|
518136
518194
|
Object.assign(this.state.appState, effectivePatch);
|
|
518137
518195
|
if ("planMode" in effectivePatch) this.updateEditorBorderHighlight();
|
|
518138
518196
|
this.state.loopIndicator.setState(this.state.appState);
|
|
@@ -518144,6 +518202,7 @@ var BlunTUI = class {
|
|
|
518144
518202
|
this.updateQueueDisplay();
|
|
518145
518203
|
this.sessionEventHandler.retryQueuedGoalPromotion();
|
|
518146
518204
|
}
|
|
518205
|
+
if (goalTimeTriggerChanged) queueMicrotask(() => this.syncGoalTimeTrigger());
|
|
518147
518206
|
if (additionalDirsChanged) this.setupAutocomplete();
|
|
518148
518207
|
this.state.ui.requestRender();
|
|
518149
518208
|
}
|
|
@@ -518282,6 +518341,7 @@ var BlunTUI = class {
|
|
|
518282
518341
|
}
|
|
518283
518342
|
unloadCurrentSession(reason) {
|
|
518284
518343
|
const previous = this.session;
|
|
518344
|
+
this.goalTimeTriggerController.reset();
|
|
518285
518345
|
this.managedQuotaWarningController.dismiss();
|
|
518286
518346
|
this.sessionEventUnsubscribe?.();
|
|
518287
518347
|
this.sessionEventUnsubscribe = void 0;
|
|
@@ -518334,6 +518394,7 @@ var BlunTUI = class {
|
|
|
518334
518394
|
}
|
|
518335
518395
|
resetSessionRuntime() {
|
|
518336
518396
|
this.aborted = false;
|
|
518397
|
+
this.goalTimeTriggerController.reset();
|
|
518337
518398
|
this.managedQuotaWarningController.dismiss();
|
|
518338
518399
|
this.streamingUI.discardPending();
|
|
518339
518400
|
this.streamingUI.disposeActiveCompactionBlock();
|