@worca/app 1.1.1 → 1.2.0-rc.2
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/README.md +23 -1
- package/package.json +1 -1
- package/src/cli/worca-cc.mjs +247 -27
- package/src/core/artifacts.mjs +10 -2
- package/src/core/ask/attachment-kind.mjs +95 -0
- package/src/core/ask/events.mjs +42 -3
- package/src/core/ask/follow.mjs +10 -4
- package/src/core/ask/limits.mjs +6 -3
- package/src/core/ask/prompt.mjs +37 -12
- package/src/core/ask/spawn.mjs +6 -3
- package/src/core/ask/store.mjs +89 -11
- package/src/core/ask/tool-deps.mjs +27 -3
- package/src/core/ask/tools.mjs +41 -10
- package/src/core/ask/turn.mjs +58 -12
- package/src/core/chat/command-router.mjs +8 -4
- package/src/core/chat/notifier.mjs +6 -1
- package/src/core/chat/renderers.mjs +15 -8
- package/src/core/claude-runner.mjs +120 -18
- package/src/core/config.mjs +46 -3
- package/src/core/db.mjs +92 -9
- package/src/core/failure-policy.mjs +201 -0
- package/src/core/graph/scheduler.mjs +8 -1
- package/src/core/host-guard.mjs +271 -0
- package/src/core/model-env.mjs +68 -0
- package/src/core/orchestrator.mjs +128 -35
- package/src/core/plugin-shim.mjs +3 -3
- package/src/core/run-harness.mjs +410 -61
- package/src/core/settings.mjs +76 -1
- package/src/core/ui-instance.mjs +235 -0
- package/ui/public/app.js +259 -39
- package/ui/public/ask-model.mjs +60 -7
- package/ui/public/ask-panel.mjs +314 -65
- package/ui/public/index.html +42 -0
- package/ui/public/style.css +28 -0
- package/ui/server.mjs +377 -80
package/src/core/model-env.mjs
CHANGED
|
@@ -83,6 +83,74 @@ export function prepareModelEnv(modelEnv, sourceEnv = process.env) {
|
|
|
83
83
|
return { env, dropped };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// ── env flags + masking (shared by claude-runner.mjs, plugin-shim.mjs, ui/server.mjs)
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The ONE "is this env flag on" rule for worca's own knobs (WORCA_MOCK,
|
|
90
|
+
* WORCA_SUBAGENT_HOOKS, WORCA_DEBUG_SPAWN, …): a denylist — anything but unset,
|
|
91
|
+
* "", "0" and "false" (any case) is on. Several names may be given; the first
|
|
92
|
+
* one that is set wins (WORCA_MOCK ?? ORCH_MOCK). Lives in this zero-import leaf
|
|
93
|
+
* so every gate shares it instead of hand-copying the comparison.
|
|
94
|
+
* @param {...string} names
|
|
95
|
+
*/
|
|
96
|
+
export function envFlag(...names) {
|
|
97
|
+
let v;
|
|
98
|
+
for (const n of names) { v = process.env[n]; if (v !== undefined) break; }
|
|
99
|
+
return !!v && v !== '0' && v.toLowerCase() !== 'false';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Mask a model-env VALUE for an operator-facing display (the Models editor):
|
|
104
|
+
* six bullets + the last 4 chars when longer than 8, else six bullets. The
|
|
105
|
+
* `••` prefix is what ui/server.mjs#isMaskedEcho keys on to treat an echoed
|
|
106
|
+
* value as "keep", so the shape is a contract — change both together. For LOG
|
|
107
|
+
* lines use describeModelEnvEntry: a per-spawn log must not carry a suffix.
|
|
108
|
+
*/
|
|
109
|
+
export function maskModelEnvValue(v) {
|
|
110
|
+
const s = String(v ?? '');
|
|
111
|
+
return s.length > 8 ? `••••••${s.slice(-4)}` : '••••••';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Keys whose value is routing configuration, not a credential, and therefore
|
|
115
|
+
// SAFE to print in a spawn log: which endpoint / which wire id a spawn used is
|
|
116
|
+
// exactly the diagnostic question, and masking them makes two gateway cards
|
|
117
|
+
// indistinguishable. Everything else (ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY,
|
|
118
|
+
// ANTHROPIC_CUSTOM_HEADERS, plugin {secret} values, …) is treated as a secret.
|
|
119
|
+
const READABLE_MODEL_ENV_KEYS = new Set([
|
|
120
|
+
'ANTHROPIC_MODEL', 'ANTHROPIC_BASE_URL', 'ANTHROPIC_SMALL_FAST_MODEL',
|
|
121
|
+
]);
|
|
122
|
+
const READABLE_MODEL_ENV_KEY_RES = [/^ANTHROPIC_DEFAULT_[A-Z0-9]+_MODEL$/, /^CLAUDE_CODE_USE_[A-Z0-9]+$/];
|
|
123
|
+
|
|
124
|
+
/** Whether a model-env key's value may be printed verbatim in a log line. */
|
|
125
|
+
export function isReadableModelEnvKey(key) {
|
|
126
|
+
return typeof key === 'string'
|
|
127
|
+
&& (READABLE_MODEL_ENV_KEYS.has(key) || READABLE_MODEL_ENV_KEY_RES.some((re) => re.test(key)));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* One `KEY=value` fragment for a log line. Readable keys print their value
|
|
132
|
+
* (a URL with userinfo has the credentials stripped; an unparsable URL is
|
|
133
|
+
* treated as a secret); every other key prints `<set, N chars>` — presence and
|
|
134
|
+
* length prove the env reached the spawn without leaking any part of it.
|
|
135
|
+
*/
|
|
136
|
+
export function describeModelEnvEntry(key, value) {
|
|
137
|
+
const s = String(value ?? '');
|
|
138
|
+
const secret = `<set, ${s.length} chars>`;
|
|
139
|
+
if (!isReadableModelEnvKey(key)) return `${key}=${secret}`;
|
|
140
|
+
if (key === 'ANTHROPIC_BASE_URL') {
|
|
141
|
+
let u;
|
|
142
|
+
try { u = new URL(s); } catch { return `${key}=${secret}`; }
|
|
143
|
+
if (u.username || u.password) { u.username = ''; u.password = ''; }
|
|
144
|
+
return `${key}=${u.href}`;
|
|
145
|
+
}
|
|
146
|
+
return `${key}=${s}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The sorted, log-safe `KEY=value, …` rendering of a whole model env. */
|
|
150
|
+
export function describeModelEnv(env) {
|
|
151
|
+
return Object.keys(env || {}).sort().map((k) => describeModelEnvEntry(k, env[k])).join(', ');
|
|
152
|
+
}
|
|
153
|
+
|
|
86
154
|
// ── per-model cost override (opt-in pricing, config.mjs resolveModelCost) ─────
|
|
87
155
|
// Lives HERE for the same reason the env policy does: BOTH catalog layers must
|
|
88
156
|
// validate it against one rule. settings.mjs owns the user's global catalog and
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from './artifacts.mjs';
|
|
33
33
|
import { readQuestionsFile } from './protocol.mjs';
|
|
34
34
|
import { classifyError } from './recoverable-error.mjs';
|
|
35
|
+
import { resolveFailure, markTerminal, isTerminal } from './failure-policy.mjs';
|
|
35
36
|
|
|
36
37
|
/** Max ask-then-resume question rounds per execution (mirrors v1's constant). */
|
|
37
38
|
const MAX_QUESTION_ROUNDS = 3;
|
|
@@ -145,14 +146,30 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
145
146
|
return this._buildResumePoint(null);
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/** hook: the last clean point (see run-harness.mjs). _graphSnapshot is never cleared
|
|
150
|
+
* (onSnapshot, the resume restore), and the scheduler's finish() takes one final
|
|
151
|
+
* snapshot, so after a clean 'done' this IS the all-terminal point. */
|
|
152
|
+
_engineLastPoint() {
|
|
153
|
+
return this._graphSnapshot ? this._buildResumePoint(this._graphSnapshot) : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** hook: agent keys for a setup replay — from the frozen manifest, never the workflow row. */
|
|
157
|
+
_engineAgentKeys() {
|
|
158
|
+
if (this.resolved?.agentKeys) return new Set(this.resolved.agentKeys);
|
|
159
|
+
const manifest = this.state.stepper;
|
|
160
|
+
return manifest ? new Set(resolvedFromManifest(manifest, this.registry).agentKeys) : new Set();
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
// ── hook 2: run the graph ──────────────────────────────────────────────────
|
|
149
164
|
/**
|
|
150
165
|
* The scheduler owns readiness, loop budgets, gates and End; this method owns
|
|
151
166
|
* the process side: the resume-time restoration (Task 6), the pre-rendered
|
|
152
167
|
* task document, the executor binding, the event fan-out and the resume-v2
|
|
153
|
-
* snapshot. Returns 'done' | 'paused';
|
|
154
|
-
*
|
|
155
|
-
* classifies it exactly as v1 does.
|
|
168
|
+
* snapshot. Returns 'done' | 'paused'; only the user's STOP is re-thrown (its
|
|
169
|
+
* AbortError/plain-error identity intact) so the base run()/resume() catch
|
|
170
|
+
* classifies it exactly as v1 does. Every other failure pauses inside _execute
|
|
171
|
+
* (errors never end a run), and a pause that lands after the End card fired
|
|
172
|
+
* returns 'paused', never 'done'.
|
|
156
173
|
* @param {{resume?:object|null, rehydrated?:object|null}} [o] the base passes
|
|
157
174
|
* `{ resume: rp, rehydrated }` on a resume and `{ resume: null }` on a fresh run.
|
|
158
175
|
* @returns {Promise<'done'|'paused'>}
|
|
@@ -212,6 +229,16 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
212
229
|
// agent's error otherwise). A scheduler abort with nothing recorded yet is a stop.
|
|
213
230
|
throw this._graphError || (this.abort.signal.aborted ? abortError('stopped') : new Error('a graph execution failed'));
|
|
214
231
|
}
|
|
232
|
+
if (outcome === 'done' && this.pauseRequested) {
|
|
233
|
+
// D17: the scheduler resolves `ended` BEFORE it looks at pauseRequested
|
|
234
|
+
// (scheduler.mjs:1033-1034), so a pause that landed on a straggler after the
|
|
235
|
+
// End card fired — an error-pause, or the user's — came back as 'done'. It is
|
|
236
|
+
// a pause: the point was frozen by onSnapshot the moment pause() ran (the
|
|
237
|
+
// straggler's row is still non-terminal in it), reattach() re-invokes that
|
|
238
|
+
// row on resume and the restored `ended` then quiesces the run to done.
|
|
239
|
+
this.state.resumePoint = this._buildResumePoint(this._graphSnapshot);
|
|
240
|
+
return 'paused';
|
|
241
|
+
}
|
|
215
242
|
if (outcome === 'paused') {
|
|
216
243
|
this.state.resumePoint = this._buildResumePoint(this._graphSnapshot);
|
|
217
244
|
return 'paused';
|
|
@@ -256,6 +283,7 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
256
283
|
checkpointRefs: { ...this.checkpointRefs },
|
|
257
284
|
workspace: this.isWorkspace ? { projects: this._workspaceProjects() } : null,
|
|
258
285
|
pauseReason: this.pauseReason || null,
|
|
286
|
+
pauseDetail: this.pauseDetail || null,
|
|
259
287
|
// The EFFECTIVE instruction at dispatch time (post in-worktree graph
|
|
260
288
|
// build), not the detect-time tools.instruction.
|
|
261
289
|
toolInstruction: this.toolInstruction ?? '',
|
|
@@ -401,16 +429,35 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
401
429
|
const nc = (this.resolved.nodeCtx || {})[node.id] || { nodeId: node.id, kind: node.kind, key: null };
|
|
402
430
|
// The composite protocol: these three modes are the process side of a
|
|
403
431
|
// fan-out — they spawn nothing, record no ledger row and allocate nothing,
|
|
404
|
-
// so a composite shell never burns a plan version.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
432
|
+
// so a composite shell never burns a plan version. Same policy as below: a
|
|
433
|
+
// throw pauses the run. Only the `finish` answer is read as a settlement
|
|
434
|
+
// (settle -> pausedExecution); runComposite ignores an `expand` answer
|
|
435
|
+
// without `phases` (it falls to runUnexpanded) and runPhase ignores the
|
|
436
|
+
// `phase` answers (scheduler.mjs:447/:469), so for those two the pause lands
|
|
437
|
+
// one call later, at the next ordinary execute's _checkPause() — the shell
|
|
438
|
+
// row ends 'paused' either way and the whole fan-out re-runs on resume.
|
|
439
|
+
if (args.composite) {
|
|
440
|
+
try {
|
|
441
|
+
if (args.composite === 'expand') return await this._expandDecomposition(node, args);
|
|
442
|
+
if (args.composite === 'phase') return this._compositePhase(args);
|
|
443
|
+
return await this._finishComposite(nc, args);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
return this._settleUnstarted(nc, node, args, err);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
408
448
|
|
|
409
|
-
|
|
449
|
+
let ctx;
|
|
450
|
+
try {
|
|
451
|
+
ctx = this._execCtx(node, nc, args);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
return this._settleUnstarted(nc, node, args, err); // allocation failed: no row to mark
|
|
454
|
+
}
|
|
410
455
|
this._execStep(ctx, 'start');
|
|
411
|
-
if (ctx.slice) updateTaskStatus(this.pipeline.id, ctx.slice.id, 'running', new Date().toISOString());
|
|
412
456
|
let endMark = 'done';
|
|
413
457
|
try {
|
|
458
|
+
// A real DB write — inside the try: a throw here pauses like anything
|
|
459
|
+
// else, and the finally skips updateTaskStatus for a 'paused' mark.
|
|
460
|
+
if (ctx.slice) updateTaskStatus(this.pipeline.id, ctx.slice.id, 'running', new Date().toISOString());
|
|
414
461
|
// Exactly what v1's dispatcher loop does at every step boundary
|
|
415
462
|
// (orchestrator.mjs:259-261): a stop or pause requested while nothing was
|
|
416
463
|
// in flight (e.g. between executions, or during a flow card) must land
|
|
@@ -435,17 +482,47 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
435
482
|
endMark = 'paused';
|
|
436
483
|
return { paused: true };
|
|
437
484
|
}
|
|
438
|
-
|
|
439
|
-
|
|
485
|
+
if (this.abort.signal.aborted || this.state.status === 'stopped') {
|
|
486
|
+
// The user's stop — the ONLY path that still lets the scheduler see a
|
|
487
|
+
// failure. Its identity is kept for _engineRun's rethrow; the shell's catch
|
|
488
|
+
// classifies the run 'stopped' (status/isAbort), never 'error'. This also
|
|
489
|
+
// covers a child that died with a PLAIN error after the stop landed — that
|
|
490
|
+
// error still gets today's one error-level line (_logStepFailure skips
|
|
491
|
+
// AbortErrors itself).
|
|
492
|
+
endMark = 'stopped';
|
|
493
|
+
this._graphError ||= err; // preserve identity for the base catch
|
|
494
|
+
this._logStepFailure(nc, ctx, err);
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
// The FLOW site (failure-policy.mjs): anything that escaped _runNodeAttempts'
|
|
498
|
+
// own verdict — a flow card, the questions loop, _afterExecution, an
|
|
499
|
+
// unexpected throw — is decided here. A verdict the node site already issued
|
|
500
|
+
// (a terminal error) is enacted, never re-decided.
|
|
501
|
+
const verdict = isTerminal(err) ? { outcome: 'error' }
|
|
502
|
+
: resolveFailure({ site: 'flow', cls: classifyError(err), auto: this.auto });
|
|
503
|
+
if (verdict.outcome === 'pause') {
|
|
504
|
+
// pause() rejects a sibling parked on a recovery prompt with the pause
|
|
505
|
+
// sentinel, so that slice unwinds as paused too.
|
|
506
|
+
this._pauseFor(verdict.reason, err, { nc, ctx });
|
|
507
|
+
endMark = 'paused';
|
|
508
|
+
return { paused: true };
|
|
509
|
+
}
|
|
510
|
+
// Terminal: the scheduler sees the failure (its row ends 'error', in-flight
|
|
511
|
+
// siblings 'skipped') and _engineRun rethrows it for the shell's error path.
|
|
512
|
+
endMark = 'error';
|
|
513
|
+
this._graphError ||= markTerminal(err); // preserve identity for the base catch
|
|
440
514
|
this._logStepFailure(nc, ctx, err);
|
|
441
515
|
// A sibling slice parked on an interactive recovery prompt is not
|
|
442
516
|
// signal-reachable (_ask settles only via answer()/pause()/stop()), so a
|
|
443
|
-
// genuine slice failure rejects that prompt
|
|
444
|
-
//
|
|
517
|
+
// genuine slice failure rejects that prompt — the phase is failing and must
|
|
518
|
+
// not wait on a now-meaningless answer.
|
|
445
519
|
if (ctx.slice && this.pendingQuestion?.kind === 'recovery') {
|
|
446
520
|
const pq = this.pendingQuestion;
|
|
447
521
|
this.pendingQuestion = null;
|
|
448
|
-
|
|
522
|
+
// Stamped terminal: the released sibling's catch must ENACT this verdict
|
|
523
|
+
// (its row ends 'error' with the phase), never re-decide it at the flow site
|
|
524
|
+
// as a pause with the detail 'aborted'.
|
|
525
|
+
pq.reject(markTerminal(abortError()));
|
|
449
526
|
}
|
|
450
527
|
throw err;
|
|
451
528
|
} finally {
|
|
@@ -458,6 +535,30 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
458
535
|
}
|
|
459
536
|
}
|
|
460
537
|
|
|
538
|
+
/** A throw before the execution had a ledger row (the composite shell modes, or
|
|
539
|
+
* _execCtx's allocation): the SAME outcomes as _execute's catch, in the same
|
|
540
|
+
* order and with the same conditions — the FLOW site — just without a row to
|
|
541
|
+
* mark. args.executionId / args.ordinal exist on every composite call (argsFor,
|
|
542
|
+
* scheduler.mjs:333-342, is spread into each of them). */
|
|
543
|
+
_settleUnstarted(nc, node, args, err) {
|
|
544
|
+
if (isPause(err) || (this.pauseRequested && (isAbort(err) || this.pauseAbort.signal.aborted))) return { paused: true };
|
|
545
|
+
const ctx = { nodeId: node.id, executionId: args.executionId, ordinal: args.ordinal || 1 };
|
|
546
|
+
if (this.abort.signal.aborted || this.state.status === 'stopped') {
|
|
547
|
+
this._graphError ||= err; // the stop keeps its identity for _engineRun's rethrow
|
|
548
|
+
this._logStepFailure(nc, ctx, err);
|
|
549
|
+
throw err;
|
|
550
|
+
}
|
|
551
|
+
const verdict = isTerminal(err) ? { outcome: 'error' }
|
|
552
|
+
: resolveFailure({ site: 'flow', cls: classifyError(err), auto: this.auto });
|
|
553
|
+
if (verdict.outcome === 'pause') {
|
|
554
|
+
this._pauseFor(verdict.reason, err, { nc, ctx });
|
|
555
|
+
return { paused: true };
|
|
556
|
+
}
|
|
557
|
+
this._graphError ||= markTerminal(err);
|
|
558
|
+
this._logStepFailure(nc, ctx, err);
|
|
559
|
+
throw err;
|
|
560
|
+
}
|
|
561
|
+
|
|
461
562
|
/** The five flow cards through P3's dispatcher. Engine-owned: instant, $0, no
|
|
462
563
|
* semaphore slot, no spawn. runExecution reads ctx.taskArtifact (Task card),
|
|
463
564
|
* ctx.allocatedPath (Combine) and derives Combine's headings from ctx.template. */
|
|
@@ -675,9 +776,12 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
675
776
|
this._persist().catch(() => {});
|
|
676
777
|
}
|
|
677
778
|
|
|
678
|
-
/** The
|
|
679
|
-
*
|
|
680
|
-
*
|
|
779
|
+
/** The retry loop around ONE execution — the NODE site of failure-policy.mjs.
|
|
780
|
+
* _recover() resolves the verdict (running the backoff or the recovery prompt
|
|
781
|
+
* on the way); this loop enacts it. A pause throws pauseErr() with
|
|
782
|
+
* pauseRequested already set (_pauseFor calls pause()), so _execute's catch
|
|
783
|
+
* reproduces the 'paused' mark; a terminal error is stamped so _execute's
|
|
784
|
+
* catch enacts it instead of re-deciding at the flow site. */
|
|
681
785
|
async _runNodeAttempts(nc, ctx) {
|
|
682
786
|
for (let attempt = 1; ; attempt++) {
|
|
683
787
|
try {
|
|
@@ -685,12 +789,13 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
685
789
|
} catch (err) {
|
|
686
790
|
if (this.pauseRequested && (isAbort(err) || isPause(err) || this.pauseAbort.signal.aborted)) throw pauseErr();
|
|
687
791
|
if (isAbort(err) || isPause(err)) throw err;
|
|
792
|
+
if (this.abort.signal.aborted || this.state.status === 'stopped') throw err; // a stop is in flight: _execute marks it 'stopped'
|
|
688
793
|
const cls = classifyError(err);
|
|
689
|
-
|
|
690
|
-
if (
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
|
|
794
|
+
const verdict = await this._recover({ node: { key: nc.key || ctx.nodeId }, cls, err, attempt });
|
|
795
|
+
if (this.pauseRequested) throw pauseErr(); // a pause landed during backoff/prompt: its reason stands
|
|
796
|
+
if (verdict.outcome === 'retry') { this._execStep(ctx, 'start'); continue; } // back to running for the retry
|
|
797
|
+
if (verdict.outcome === 'pause') { this._pauseFor(verdict.reason, err, { nc, ctx, cls }); throw pauseErr(); }
|
|
798
|
+
throw markTerminal(err); // terminal: _execute's catch ends the run
|
|
694
799
|
}
|
|
695
800
|
}
|
|
696
801
|
}
|
|
@@ -724,18 +829,6 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
724
829
|
});
|
|
725
830
|
}
|
|
726
831
|
|
|
727
|
-
/** A session/usage cap that only clears after a multi-hour reset: pause the run
|
|
728
|
-
* (v1's _pauseForLimit, orchestrator.mjs:756, re-keyed by execution). */
|
|
729
|
-
_pauseForLimit(nc, ctx, err) {
|
|
730
|
-
const label = nc.key || ctx.nodeId;
|
|
731
|
-
const reason = firstLine(err?.message || String(err));
|
|
732
|
-
if (!this.pauseReason) this.pauseReason = reason;
|
|
733
|
-
this._log(label, 'warn', `session/usage limit reached — pausing for manual resume: ${reason}`,
|
|
734
|
-
{ nodeId: ctx.nodeId, executionId: ctx.executionId, cycle: ctx.ordinal });
|
|
735
|
-
appendAudit(this.pipeline.dir, `Pipeline **paused**: session/usage limit on ${label} — ${reason}. Resume after the reset.`).catch(() => {});
|
|
736
|
-
this.pause();
|
|
737
|
-
}
|
|
738
|
-
|
|
739
832
|
/**
|
|
740
833
|
* Everything between an agent returning and the scheduler publishing its tokens:
|
|
741
834
|
* - the verdict lands in the AUTHORITATIVE reviews table, keyed by the generic
|
|
@@ -978,7 +1071,7 @@ export class GraphOrchestrator extends RunHarness {
|
|
|
978
1071
|
this._resumeSnapshot = rp.snapshot || null;
|
|
979
1072
|
this._graphSnapshot = rp.snapshot || null;
|
|
980
1073
|
this._planVersion = Number.isFinite(rp.planVersion) ? rp.planVersion : 0;
|
|
981
|
-
this.
|
|
1074
|
+
this._clearPauseReason();
|
|
982
1075
|
const manifest = rp.manifest || this.state.stepper;
|
|
983
1076
|
this.state.stepper = manifest;
|
|
984
1077
|
this._adoptResolvedGraph(resolvedFromManifest(manifest, this.registry));
|
package/src/core/plugin-shim.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { spawn } from 'node:child_process';
|
|
|
16
16
|
import { readFileSync } from 'node:fs';
|
|
17
17
|
import { join, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { envFlag } from './model-env.mjs';
|
|
19
20
|
import { WORCA_PLUGIN_API } from './plugin-api.mjs';
|
|
20
21
|
import { normalizeManifest, negotiatedApi } from './plugin-manifest.mjs';
|
|
21
22
|
import { readPluginsLock, pluginCurrentDir } from './plugins-lock.mjs';
|
|
@@ -66,10 +67,9 @@ const MOCK_DEFAULTS = {
|
|
|
66
67
|
validateConfig: () => ({ ok: true }),
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
/** Same env-flag semantics as claude-runner.mjs#mockEnabled
|
|
70
|
+
/** Same env-flag semantics as claude-runner.mjs#mockEnabled — one shared rule. */
|
|
70
71
|
function mockMode() {
|
|
71
|
-
|
|
72
|
-
return !!v && v !== '0' && v.toLowerCase() !== 'false';
|
|
72
|
+
return envFlag('WORCA_MOCK', 'ORCH_MOCK');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
async function mockCall(op, args) {
|