@wjarka/cezarion 0.14.5 → 0.14.6
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 +4 -1
- package/dist/automations/types.d.ts +2 -0
- package/dist/contract/automations.d.ts +8 -0
- package/dist/contract/delegation.d.ts +1 -0
- package/dist/contract/index.js +5 -1
- package/dist/contract/runs.d.ts +7 -0
- package/dist/contract/skills.d.ts +1 -0
- package/dist/contract/workflows.d.ts +6 -0
- package/dist/delegation/cli.js +5 -3
- package/dist/delegation/cli.js.map +1 -1
- package/dist/delegation/execution-identity.d.ts +92 -4
- package/dist/delegation/execution-identity.js +40 -10
- package/dist/delegation/execution-identity.js.map +1 -1
- package/dist/delegation/provision.js +5 -2
- package/dist/delegation/provision.js.map +1 -1
- package/dist/delegation/routes.d.ts +1 -0
- package/dist/delegation/service.js +95 -13
- package/dist/delegation/service.js.map +1 -1
- package/dist/delegation/transport.d.ts +1 -0
- package/dist/runs/store.d.ts +1 -0
- package/dist/server/server.d.ts +68 -0
- package/dist/workflows/load.d.ts +2 -0
- package/dist/workflows/load.js +5 -0
- package/dist/workflows/load.js.map +1 -1
- package/dist/workflows/run.d.ts +37 -2
- package/dist/workflows/run.js +160 -47
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +5 -1
- package/dist/workflows/types.js +6 -2
- package/dist/workflows/types.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/{agent-accounts-4EqNShyp.js → agent-accounts-lP_xK83o.js} +1 -1
- package/web/dist/assets/{alert-dialog-CVF_qvQ8.js → alert-dialog-CmDuefYh.js} +1 -1
- package/web/dist/assets/{collapsible-DVDGHZAE.js → collapsible-BqX8REmY.js} +1 -1
- package/web/dist/assets/{commit-list-BuNsBn8P.js → commit-list-BWvv1H7A.js} +1 -1
- package/web/dist/assets/{compare-variants-BnQ1O8Ix.js → compare-variants-Bg7eLW3z.js} +1 -1
- package/web/dist/assets/{diff-DWmKlqw52.js → diff-0GBum40Q2.js} +2 -2
- package/web/dist/assets/{diff-controls-CuqRa-63.js → diff-controls-HwWHHait.js} +1 -1
- package/web/dist/assets/{diff-stat-BXriPIds.js → diff-stat-mNkhAc5m.js} +1 -1
- package/web/dist/assets/{diff-view-DLPDcgEV.js → diff-view-DW64doDY.js} +1 -1
- package/web/dist/assets/{dropdown-menu-IsTcumXE.js → dropdown-menu-sZjYPcLh.js} +1 -1
- package/web/dist/assets/{git-toolbar-BjMzETip.js → git-toolbar-DBW5EIcY.js} +1 -1
- package/web/dist/assets/{github-BvowTAuy.js → github-CkZ4hWI5.js} +1 -1
- package/web/dist/assets/{image-preview-WXKTY8KT.js → image-preview-DZicGNkU.js} +1 -1
- package/web/dist/assets/{index-Cz3N9b18.js → index-DWWI-B9n.js} +3 -3
- package/web/dist/assets/{markdown-gFxX4dDH.js → markdown-AY2K3kIV.js} +1 -1
- package/web/dist/assets/{pill-BlaWNkHk.js → pill-CcJqA1cL.js} +1 -1
- package/web/dist/assets/{project-router-CX1jLqj-.js → project-router-SwsXRKXs.js} +1 -1
- package/web/dist/assets/{prompt-templates-DwdQcfSc.js → prompt-templates-iV2WD2jy.js} +1 -1
- package/web/dist/assets/{repo-git-DnbE8dR5.js → repo-git-BW3TOs3U.js} +1 -1
- package/web/dist/assets/{run-diff-Mfc85irR.js → run-diff-DPUNj56q.js} +1 -1
- package/web/dist/assets/{run-header-CDO3LV4z.js → run-header-DbDluWoV.js} +1 -1
- package/web/dist/assets/{skills-DHfsyXTQ.js → skills-Dv8Vwv5D.js} +1 -1
- package/web/dist/assets/{tab-link-DhREnUYE.js → tab-link-DSftvkZS.js} +1 -1
- package/web/dist/assets/{task-changes-Cfo-vtEl.js → task-changes-CTCuVls1.js} +1 -1
- package/web/dist/assets/{task-commits-CvK4flXT.js → task-commits-weJQ5GSl.js} +1 -1
- package/web/dist/assets/{task-files-BwVGUObK.js → task-files-B4bUimY8.js} +1 -1
- package/web/dist/assets/{task-thread-g1vc4tKn.js → task-thread-BuXrVB9K.js} +1 -1
- package/web/dist/assets/{textarea-DCP8bOOf.js → textarea-CxRdTWlq.js} +1 -1
- package/web/dist/assets/{thread-loading-D9DbIsIS.js → thread-loading-JlP6dUpX.js} +1 -1
- package/web/dist/assets/{token-metrics-PSnNTJZF.js → token-metrics-Z3UFYbuD.js} +1 -1
- package/web/dist/assets/{utils-DNvmfH6b.js → utils-CUnaAifW.js} +1 -1
- package/web/dist/assets/{workflows-BDdUJIFT.js → workflows-vA27Yi8W.js} +3 -3
- package/web/dist/assets/{zoomable-image-yNqiuVu7.js → zoomable-image-DUE1KFJ0.js} +1 -1
- package/web/dist/index.html +13 -13
package/dist/workflows/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { workerContextHash, captureWorkerAccount, boundWorkerAccountEnv, WorkerIdentityError } from '../delegation/execution-identity.js';
|
|
1
|
+
import { workerContextHash, workerWorkflowHash, workerStepIdentity, captureWorkerAccount, boundWorkerAccountEnv, WorkerIdentityError } from '../delegation/execution-identity.js';
|
|
2
2
|
import { buildChildEnv } from '../core/agent-env.js';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
@@ -796,8 +796,12 @@ export class RunManager {
|
|
|
796
796
|
* spawning a CLI. Never throws: an unreadable home degrades to the default profile, which is
|
|
797
797
|
* exactly the behaviour that predates profiles.
|
|
798
798
|
*/
|
|
799
|
-
/**
|
|
800
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Ordinary runs retain mutable discovery; owned runs require explicit durable evidence. With a
|
|
801
|
+
* `stepId`, the step's own pinned entry is checked too (#452): its account must still be
|
|
802
|
+
* available and its model/effort must clear the lock, exactly as the run-level fields must.
|
|
803
|
+
*/
|
|
804
|
+
workerIdentity(runId, stepId) {
|
|
801
805
|
const run = this.store.getRun(runId);
|
|
802
806
|
if (run?.delegation?.role !== 'worker')
|
|
803
807
|
return undefined;
|
|
@@ -809,6 +813,7 @@ export class RunManager {
|
|
|
809
813
|
if (identity.contextHash !== undefined && (!run.delegation.context || workerContextHash(run.delegation.context) !== identity.contextHash)) {
|
|
810
814
|
throw new WorkerIdentityError('Accepted worker context recipe is unavailable or changed');
|
|
811
815
|
}
|
|
816
|
+
this.ownedWorkflow(run, identity);
|
|
812
817
|
if (agentModelsLocked(this.repoRoot) && (identity.model !== undefined || identity.effort !== undefined)) {
|
|
813
818
|
throw new WorkerIdentityError(`Accepted worker settings cannot run: ${AGENT_MODELS_LOCKED_ERROR}`);
|
|
814
819
|
}
|
|
@@ -816,17 +821,69 @@ export class RunManager {
|
|
|
816
821
|
run.model !== identity.model || run.effort !== identity.effort) {
|
|
817
822
|
throw new WorkerIdentityError('Accepted worker execution identity does not match its run');
|
|
818
823
|
}
|
|
819
|
-
|
|
824
|
+
// Availability is checked for the account about to be USED: the selected step's entry when
|
|
825
|
+
// there is one, and the single run-level account only for a legacy identity that has no
|
|
826
|
+
// per-step list. A per-step identity checked without a step (the evidence-only calls in
|
|
827
|
+
// `execute`, `runContinuation` and `ownedJob`) binds nothing here — the launch resolves its
|
|
828
|
+
// step and binds that account — so a first-step login removed after its step finished never
|
|
829
|
+
// stops a later step, a Continue or a recovery whose own account is intact (#465).
|
|
830
|
+
const step = stepId === undefined ? undefined : workerStepIdentity(identity, stepId);
|
|
831
|
+
if (step && agentModelsLocked(this.repoRoot) && (step.model !== undefined || step.effort !== undefined)) {
|
|
832
|
+
throw new WorkerIdentityError(`Accepted worker settings cannot run: ${AGENT_MODELS_LOCKED_ERROR}`);
|
|
833
|
+
}
|
|
834
|
+
const account = step?.account ?? (identity.steps === undefined ? identity.account : undefined);
|
|
835
|
+
if (account)
|
|
836
|
+
boundWorkerAccountEnv(account, buildChildEnv({ backend: account.provider }));
|
|
820
837
|
return identity;
|
|
821
838
|
}
|
|
839
|
+
/** The pinned identity one agent step of an owned worker runs under; undefined for ordinary and internal runs. */
|
|
840
|
+
workerStep(runId, stepId) {
|
|
841
|
+
const identity = this.workerIdentity(runId, stepId);
|
|
842
|
+
if (!identity)
|
|
843
|
+
return undefined;
|
|
844
|
+
return stepId === undefined
|
|
845
|
+
? { account: identity.account, grants: identity.grants, ...(identity.model === undefined ? {} : { model: identity.model }), ...(identity.effort === undefined ? {} : { effort: identity.effort }) }
|
|
846
|
+
: workerStepIdentity(identity, stepId);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The steps a continuation may extend. An accepted worker's public `workflowDef` is authority
|
|
850
|
+
* only while its identity binds it: a catalog chain through `ownedWorkflow`, and the default
|
|
851
|
+
* quick-task from the built-in constant, exactly the definition `ownedJob` launches — a salvaged
|
|
852
|
+
* or edited public record never picks which pinned identity a turn runs under.
|
|
853
|
+
*/
|
|
854
|
+
continuationDefSteps(record) {
|
|
855
|
+
if (record?.delegation?.role === 'worker') {
|
|
856
|
+
const identity = this.store.readWorkerIdentity(record.id);
|
|
857
|
+
if (identity?.kind === 'accepted')
|
|
858
|
+
return (this.ownedWorkflow(record, identity) ?? QUICK_TASK_WORKFLOW).steps;
|
|
859
|
+
}
|
|
860
|
+
return record?.workflowDef?.steps;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* The definition step a continuation extends: the one owning the resumed session, else the
|
|
864
|
+
* chain's last agent step — a synthetic `continue-N` owner and a fresh-session continuation
|
|
865
|
+
* both extend the run's tail. Shared by `continueRun` (which identity to hold the override
|
|
866
|
+
* against) and `runContinuation` (which tools, model, effort and account the turn gets).
|
|
867
|
+
*/
|
|
868
|
+
continuationDefStep(record, sessionId) {
|
|
869
|
+
const defSteps = this.continuationDefSteps(record);
|
|
870
|
+
if (defSteps === undefined)
|
|
871
|
+
return undefined;
|
|
872
|
+
const owningStep = sessionId === undefined ? undefined : record?.steps.find((s) => s.sessionId === sessionId);
|
|
873
|
+
if (sessionId !== undefined && owningStep === undefined)
|
|
874
|
+
return undefined;
|
|
875
|
+
return defSteps.find((s) => s.id === owningStep?.id) ?? [...defSteps].reverse().find((s) => stepKind(s) === 'agent');
|
|
876
|
+
}
|
|
822
877
|
async agentEnvForStep(runId, backend, options = {}) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
878
|
+
// An owned worker binds the account its identity pinned for THIS step (#452); a legacy identity
|
|
879
|
+
// answers every step with its single run-level account, so the one-provider rule still holds there.
|
|
880
|
+
const step = this.workerStep(runId, options.stepId);
|
|
881
|
+
if (step) {
|
|
882
|
+
if (backend !== step.account.provider)
|
|
826
883
|
throw new WorkerIdentityError('Accepted worker provider identity cannot change');
|
|
827
884
|
const env = this.agentEnv(runId, options.generateFollowups);
|
|
828
|
-
return { env: { ...env, ...boundWorkerAccountEnv(
|
|
829
|
-
profileId:
|
|
885
|
+
return { env: { ...env, ...boundWorkerAccountEnv(step.account, buildChildEnv({ backend, extraEnv: env })) },
|
|
886
|
+
profileId: step.account.profileId, accountBinding: step.account };
|
|
830
887
|
}
|
|
831
888
|
const run = this.store.getRun(runId);
|
|
832
889
|
const profileId = options.recordedProfileId
|
|
@@ -867,7 +924,14 @@ export class RunManager {
|
|
|
867
924
|
throw new DelegationPolicyError('incompatible_state', 'Parent session settings unavailable');
|
|
868
925
|
return structuredClone(state.delegationSettings);
|
|
869
926
|
}
|
|
870
|
-
/**
|
|
927
|
+
/**
|
|
928
|
+
* Resolve one agent step's selection before acceptance; permission grants always come from the
|
|
929
|
+
* parent. Called once per agent step of the accepted chain (#452): a same-backend step inherits
|
|
930
|
+
* the parent's account, model and effort, another runner resolves its own project/default
|
|
931
|
+
* account, and an explicit `agentProfile` names the account for either — an id the registry
|
|
932
|
+
* does not know is refused rather than degraded to the default, because a worker silently
|
|
933
|
+
* running on another login is a billing boundary crossed without anyone choosing it.
|
|
934
|
+
*/
|
|
871
935
|
async selectDelegationExecutionSettings(runId, selection) {
|
|
872
936
|
const parent = this.delegationExecutionSettings(runId);
|
|
873
937
|
const runner = selection.backend ?? parent.runner;
|
|
@@ -880,8 +944,11 @@ export class RunManager {
|
|
|
880
944
|
}
|
|
881
945
|
let accountBinding = parent.accountBinding;
|
|
882
946
|
let env = buildChildEnv({ backend: runner });
|
|
883
|
-
if (!sameBackend) {
|
|
884
|
-
const resolved = await resolveProfileEnvForRoot(this.repoRoot, runner);
|
|
947
|
+
if (!sameBackend || selection.agentProfile !== undefined) {
|
|
948
|
+
const resolved = await resolveProfileEnvForRoot(this.repoRoot, runner, selection.agentProfile);
|
|
949
|
+
if (selection.agentProfile !== undefined && resolved.profile.id !== selection.agentProfile) {
|
|
950
|
+
throw new Error(`Unknown ${runner} account "${selection.agentProfile}"`);
|
|
951
|
+
}
|
|
885
952
|
env = buildChildEnv({ backend: runner, extraEnv: resolved.env });
|
|
886
953
|
accountBinding = captureWorkerAccount(resolved.profile, env);
|
|
887
954
|
}
|
|
@@ -989,7 +1056,7 @@ export class RunManager {
|
|
|
989
1056
|
const grants = this.workerIdentity(run.id)?.grants
|
|
990
1057
|
?? run.workflowDef?.steps.find(step => step.id === QUICK_TASK_WORKFLOW.steps[0].id);
|
|
991
1058
|
return {
|
|
992
|
-
workflow: { ...QUICK_TASK_WORKFLOW, steps: [{ ...QUICK_TASK_WORKFLOW.steps[0],
|
|
1059
|
+
workflow: this.ownedWorkflow(run, this.store.readWorkerIdentity(run.id)) ?? { ...QUICK_TASK_WORKFLOW, steps: [{ ...QUICK_TASK_WORKFLOW.steps[0],
|
|
993
1060
|
...(grants?.allowedTools === undefined ? {} : { allowedTools: [...grants.allowedTools] }),
|
|
994
1061
|
...(grants?.bashAllowlist === undefined ? {} : { bashAllowlist: [...grants.bashAllowlist] }), }] },
|
|
995
1062
|
input: {
|
|
@@ -999,6 +1066,20 @@ export class RunManager {
|
|
|
999
1066
|
},
|
|
1000
1067
|
};
|
|
1001
1068
|
}
|
|
1069
|
+
/**
|
|
1070
|
+
* The catalog chain an owned worker runs (#451), or undefined for the built-in quick-task.
|
|
1071
|
+
* The public `workflowDef` is mutable state, so it is authority only while the private
|
|
1072
|
+
* identity's hash binds it — the same shape as the context recipe. A tampered or lost
|
|
1073
|
+
* definition refuses execution instead of quietly running quick-task or the edit.
|
|
1074
|
+
*/
|
|
1075
|
+
ownedWorkflow(run, identity) {
|
|
1076
|
+
if (identity?.workflowHash === undefined)
|
|
1077
|
+
return undefined;
|
|
1078
|
+
if (!run.workflowDef || workerWorkflowHash(run.workflowDef) !== identity.workflowHash) {
|
|
1079
|
+
throw new WorkerIdentityError('Accepted worker workflow definition is unavailable or changed');
|
|
1080
|
+
}
|
|
1081
|
+
return run.workflowDef;
|
|
1082
|
+
}
|
|
1002
1083
|
/**
|
|
1003
1084
|
* Parallel variants (spec 010): N runs of the same workflow on the same
|
|
1004
1085
|
* task, sharing a groupId. Variant A gets the task verbatim; B and C get a
|
|
@@ -1240,10 +1321,23 @@ export class RunManager {
|
|
|
1240
1321
|
? [...run.steps].reverse().find((step) => step.id !== queuedContinuation.id && step.sessionId)
|
|
1241
1322
|
: undefined;
|
|
1242
1323
|
if (queuedContinuation && sessionStep?.sessionId) {
|
|
1243
|
-
|
|
1324
|
+
// An accepted worker's continuation extends the step owning the resumed session, so its
|
|
1325
|
+
// runner and account come from that step's pinned identity (#452), never the run-level
|
|
1326
|
+
// first-step fields — those would re-queue a claude review's Continue as a codex turn with
|
|
1327
|
+
// no session to resume (#465). Broken identity evidence is left for the launch to report.
|
|
1328
|
+
let resumeIdentity;
|
|
1329
|
+
try {
|
|
1330
|
+
resumeIdentity = this.workerStep(run.id, this.continuationDefStep(run, sessionStep.sessionId)?.id);
|
|
1331
|
+
}
|
|
1332
|
+
catch (error) {
|
|
1333
|
+
if (!(error instanceof WorkerIdentityError))
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1336
|
+
const backend = resumeIdentity?.account.provider ?? run.runner ?? 'claude';
|
|
1244
1337
|
const sessionBackend = sessionStep.backend ?? backend;
|
|
1245
1338
|
const sessionAccount = sessionStep.profileId ?? DEFAULT_AGENT_ACCOUNT_ID;
|
|
1246
|
-
const
|
|
1339
|
+
const agentProfile = resumeIdentity?.account.profileId ?? run.agentProfile;
|
|
1340
|
+
const sameAccount = agentProfile === undefined || agentProfile === sessionAccount;
|
|
1247
1341
|
this.pendingContinuations.set(run.id, {
|
|
1248
1342
|
stepId: queuedContinuation.id,
|
|
1249
1343
|
sessionId: sessionBackend === backend && sameAccount ? sessionStep.sessionId : undefined,
|
|
@@ -3270,12 +3364,16 @@ export class RunManager {
|
|
|
3270
3364
|
if (run.delegation?.role === 'worker' && this.store.getRun(run.delegation.parentRunId)?.status === 'review') {
|
|
3271
3365
|
return { ok: false, error: 'Continue the reviewing parent before continuing its worker' };
|
|
3272
3366
|
}
|
|
3367
|
+
// Held against the identity of the step this continuation extends (#452), which for a mixed
|
|
3368
|
+
// chain is not the run-level (first-step) one; `runContinuation` resolves the same step.
|
|
3369
|
+
let resumeIdentity;
|
|
3273
3370
|
try {
|
|
3274
|
-
const
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
(opts.
|
|
3278
|
-
(opts.
|
|
3371
|
+
const resumeSession = [...run.steps].reverse().find((s) => s.sessionId)?.sessionId;
|
|
3372
|
+
resumeIdentity = this.workerStep(runId, this.continuationDefStep(run, resumeSession)?.id);
|
|
3373
|
+
if (resumeIdentity && ((opts.runner !== undefined && opts.runner !== resumeIdentity.account.provider) ||
|
|
3374
|
+
(opts.agentProfile !== undefined && opts.agentProfile !== resumeIdentity.account.profileId) ||
|
|
3375
|
+
(opts.model !== undefined && opts.model !== resumeIdentity.model) ||
|
|
3376
|
+
(opts.effort !== undefined && opts.effort !== resumeIdentity.effort))) {
|
|
3279
3377
|
return { ok: false, error: 'Accepted worker execution identity cannot change' };
|
|
3280
3378
|
}
|
|
3281
3379
|
}
|
|
@@ -3316,7 +3414,9 @@ export class RunManager {
|
|
|
3316
3414
|
const untouched = isUntouchedCancelledRun(run);
|
|
3317
3415
|
if (!sessionStep?.sessionId && !untouched)
|
|
3318
3416
|
return { ok: false, error: 'no agent session to resume' };
|
|
3319
|
-
|
|
3417
|
+
// An accepted worker continues under the identity of the step it extends, whatever the
|
|
3418
|
+
// run-level (first-step) runner says.
|
|
3419
|
+
const originalRunner = resumeIdentity?.account.provider ?? run.runner ?? (untouched ? opts.originalRunner : undefined) ?? 'claude';
|
|
3320
3420
|
const targetRunner = opts.runner ?? originalRunner;
|
|
3321
3421
|
// Session ids are provider-owned opaque values. New records carry explicit
|
|
3322
3422
|
// affinity; for legacy records, the run's current runner is the conservative
|
|
@@ -3328,7 +3428,9 @@ export class RunManager {
|
|
|
3328
3428
|
// open a fresh conversation while the thread claimed it had resumed. A step that recorded no
|
|
3329
3429
|
// account predates the feature and therefore ran under the discovered one.
|
|
3330
3430
|
const sessionAccount = sessionStep?.profileId ?? DEFAULT_AGENT_ACCOUNT_ID;
|
|
3331
|
-
|
|
3431
|
+
// A mixed-chain worker's run-level account is its FIRST step's; the resumed step's own pinned
|
|
3432
|
+
// account is what decides whether the session can be reattached (#452).
|
|
3433
|
+
const accountSwitched = (opts.agentProfile ?? resumeIdentity?.account.profileId ?? run.agentProfile ?? sessionAccount) !== sessionAccount;
|
|
3332
3434
|
const resume = sessionBackend === targetRunner && !accountSwitched;
|
|
3333
3435
|
// Follow-up runner/model/account override (#401, spec 2026-07-29-agent-profiles): the composer
|
|
3334
3436
|
// lets the user pick which backend, model and login handle this continuation — the same flat
|
|
@@ -3337,10 +3439,13 @@ export class RunManager {
|
|
|
3337
3439
|
// run's current backend — `runContinuation` reads it off the record, later continuations
|
|
3338
3440
|
// default to it, and the header reflects the active engine. An empty model ('') clears the
|
|
3339
3441
|
// pin, letting the runner pick the model (auto).
|
|
3340
|
-
|
|
3442
|
+
// An accepted worker's identity is frozen, and the check above already required any override
|
|
3443
|
+
// to equal the resumed step's entry, so there is nothing to persist — and writing a step-level
|
|
3444
|
+
// runner onto a mixed chain's record would break the run-level identity match.
|
|
3445
|
+
if (resumeIdentity === undefined && (opts.runner !== undefined ||
|
|
3341
3446
|
opts.model !== undefined ||
|
|
3342
3447
|
opts.effort !== undefined ||
|
|
3343
|
-
opts.agentProfile !== undefined) {
|
|
3448
|
+
opts.agentProfile !== undefined)) {
|
|
3344
3449
|
// Guard the pairing before persisting anything: the model override applies to the runner
|
|
3345
3450
|
// this continuation will actually use (`opts.runner ?? record.runner ?? 'claude'` — the
|
|
3346
3451
|
// same resolution `runContinuation` reads off the record). A model that is recognizably
|
|
@@ -3825,19 +3930,6 @@ export class RunManager {
|
|
|
3825
3930
|
// instead of `opus`). Fail loud here too rather than let the backend pick a default.
|
|
3826
3931
|
let continueModel;
|
|
3827
3932
|
let resumeWorkflow;
|
|
3828
|
-
try {
|
|
3829
|
-
const normalized = normalizeModelForBackend(continueBackend, agentModelsLocked(this.repoRoot) ? undefined : record?.model, { configuredProvider: await configuredModelProvider(continueBackend, state.cwd) });
|
|
3830
|
-
continueModel = normalized?.backendModel;
|
|
3831
|
-
this.store.updateRun(runId, {
|
|
3832
|
-
modelIdentity: normalized ? formatModelIdentity(normalized.identity) : undefined,
|
|
3833
|
-
});
|
|
3834
|
-
}
|
|
3835
|
-
catch (err) {
|
|
3836
|
-
if (!(err instanceof ModelIdentityError))
|
|
3837
|
-
throw err;
|
|
3838
|
-
failBeforeSpawn(err.message);
|
|
3839
|
-
return;
|
|
3840
|
-
}
|
|
3841
3933
|
// Resuming reattaches to a session that lives inside ONE account's config dir, so the
|
|
3842
3934
|
// continuation must run under the account that created it — not whatever the project has
|
|
3843
3935
|
// been switched to since. The owning step is the one carrying this session id.
|
|
@@ -3845,6 +3937,9 @@ export class RunManager {
|
|
|
3845
3937
|
? undefined
|
|
3846
3938
|
: record?.steps.find((s) => s.sessionId === sessionId);
|
|
3847
3939
|
const resumedProfileId = owningStep?.profileId;
|
|
3940
|
+
// An owned worker's turn runs under the identity pinned for the step it extends (#452).
|
|
3941
|
+
const toolsStep = this.continuationDefStep(record, sessionId);
|
|
3942
|
+
const stepIdentity = this.workerStep(runId, toolsStep?.id);
|
|
3848
3943
|
// The owning step also names the session's tools: resolve `allowedTools`/`bashAllowlist`
|
|
3849
3944
|
// from the persisted `workflowDef` exactly as the first spawn did (`runAgentStep`).
|
|
3850
3945
|
// Rebuilding with the bare DEFAULT_ALLOWED_TOOLS silently revoked every per-step grant
|
|
@@ -3855,14 +3950,26 @@ export class RunManager {
|
|
|
3855
3950
|
// owning session) both extend the run's tail, so they resolve from the definition's last
|
|
3856
3951
|
// agent step. A legacy record without `workflowDef` (#367), or a session no step owns,
|
|
3857
3952
|
// keeps today's defaults.
|
|
3858
|
-
const
|
|
3859
|
-
const toolsStep = (defSteps === undefined || (sessionId !== undefined && owningStep === undefined)
|
|
3860
|
-
? undefined
|
|
3861
|
-
: defSteps.find((s) => s.id === owningStep?.id)
|
|
3862
|
-
?? [...defSteps].reverse().find((s) => stepKind(s) === 'agent'));
|
|
3863
|
-
const grants = this.workerIdentity(runId)?.grants ?? {
|
|
3953
|
+
const grants = stepIdentity?.grants ?? {
|
|
3864
3954
|
allowedTools: allowedToolsForStep(toolsStep, continueBackend), bashAllowlist: toolsStep?.bashAllowlist,
|
|
3865
3955
|
};
|
|
3956
|
+
// The owning step's authored `model` wins over the run-level pin, exactly as the first
|
|
3957
|
+
// spawn resolved `step.model ?? input.model` (#464 review): a catalog worker keeps the
|
|
3958
|
+
// parent's inherited model at run level, so reading only the record silently switched a
|
|
3959
|
+
// step authored for another model on Continue.
|
|
3960
|
+
try {
|
|
3961
|
+
const normalized = normalizeModelForBackend(continueBackend, agentModelsLocked(this.repoRoot) ? undefined : stepIdentity ? stepIdentity.model : toolsStep?.model ?? record?.model, { configuredProvider: await configuredModelProvider(continueBackend, state.cwd) });
|
|
3962
|
+
continueModel = normalized?.backendModel;
|
|
3963
|
+
this.store.updateRun(runId, {
|
|
3964
|
+
modelIdentity: normalized ? formatModelIdentity(normalized.identity) : undefined,
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
catch (err) {
|
|
3968
|
+
if (!(err instanceof ModelIdentityError))
|
|
3969
|
+
throw err;
|
|
3970
|
+
failBeforeSpawn(err.message);
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3866
3973
|
let continueEffort;
|
|
3867
3974
|
try {
|
|
3868
3975
|
const modelsLocked = agentModelsLocked(this.repoRoot);
|
|
@@ -3872,7 +3979,7 @@ export class RunManager {
|
|
|
3872
3979
|
const stepEffort = parseDelegationEffort(toolsStep?.effort);
|
|
3873
3980
|
continueEffort = modelsLocked
|
|
3874
3981
|
? undefined
|
|
3875
|
-
: authoredStepEffort ? stepEffort : record?.effort;
|
|
3982
|
+
: stepIdentity ? stepIdentity.effort : authoredStepEffort ? stepEffort : record?.effort;
|
|
3876
3983
|
}
|
|
3877
3984
|
catch (err) {
|
|
3878
3985
|
failBeforeSpawn(err instanceof Error ? err.message : String(err));
|
|
@@ -3886,6 +3993,7 @@ export class RunManager {
|
|
|
3886
3993
|
continueProfile = await this.agentEnvForStep(runId, continueBackend, {
|
|
3887
3994
|
generateFollowups,
|
|
3888
3995
|
recordedProfileId: resumedProfileId,
|
|
3996
|
+
stepId: toolsStep?.id,
|
|
3889
3997
|
});
|
|
3890
3998
|
}
|
|
3891
3999
|
catch (err) {
|
|
@@ -4644,6 +4752,10 @@ export class RunManager {
|
|
|
4644
4752
|
}
|
|
4645
4753
|
};
|
|
4646
4754
|
const stepBackend = step.runner ?? taskBackend;
|
|
4755
|
+
// An owned worker's step runs under what its identity pinned for it (#452): model, effort,
|
|
4756
|
+
// account and grants alike. The bound `workflowDef` carries the same values; the private
|
|
4757
|
+
// evidence is the authority.
|
|
4758
|
+
const stepIdentity = this.workerStep(runId, step.id);
|
|
4647
4759
|
// Normalise the selected model to canonical `provider/model` and back to the
|
|
4648
4760
|
// backend's own wire form via the ONE shared mapper (#405). Fail-loud: an
|
|
4649
4761
|
// unresolvable model (e.g. a bare id on opencode) returns the step error
|
|
@@ -4658,8 +4770,8 @@ export class RunManager {
|
|
|
4658
4770
|
const stepEffort = parseDelegationEffort(step.effort);
|
|
4659
4771
|
effectiveEffort = modelsLocked
|
|
4660
4772
|
? undefined
|
|
4661
|
-
: authoredStepEffort ? stepEffort : this.store.getRun(runId)?.effort ?? input.effort;
|
|
4662
|
-
const normalized = normalizeModelForBackend(stepBackend, modelsLocked ? undefined : step.model ?? input.model, { configuredProvider: await configuredModelProvider(stepBackend, state.cwd) });
|
|
4773
|
+
: stepIdentity ? stepIdentity.effort : authoredStepEffort ? stepEffort : this.store.getRun(runId)?.effort ?? input.effort;
|
|
4774
|
+
const normalized = normalizeModelForBackend(stepBackend, modelsLocked ? undefined : stepIdentity ? stepIdentity.model : step.model ?? input.model, { configuredProvider: await configuredModelProvider(stepBackend, state.cwd) });
|
|
4663
4775
|
backendModel = normalized?.backendModel;
|
|
4664
4776
|
// Persist the identity of what ACTUALLY runs (#405, review M1). The run-start echo
|
|
4665
4777
|
// (line ~993) is best-effort from `taskBackend`/`input.model`; a per-step `runner`/`model`
|
|
@@ -4683,6 +4795,7 @@ export class RunManager {
|
|
|
4683
4795
|
try {
|
|
4684
4796
|
stepProfile = await this.agentEnvForStep(runId, stepBackend, {
|
|
4685
4797
|
generateFollowups: followupsEnabled() && input.generateFollowups !== false,
|
|
4798
|
+
stepId: step.id,
|
|
4686
4799
|
});
|
|
4687
4800
|
}
|
|
4688
4801
|
catch (err) {
|
|
@@ -4694,7 +4807,7 @@ export class RunManager {
|
|
|
4694
4807
|
if (state.cancelled || (beforeSpawn && this.executionBlockedByRootFinish(beforeSpawn)))
|
|
4695
4808
|
return null;
|
|
4696
4809
|
this.store.updateStep(runId, step.id, { profileId: stepProfile.profileId });
|
|
4697
|
-
const grants =
|
|
4810
|
+
const grants = stepIdentity?.grants ?? {
|
|
4698
4811
|
allowedTools: allowedToolsForStep(step, stepBackend), bashAllowlist: step.bashAllowlist,
|
|
4699
4812
|
};
|
|
4700
4813
|
state.delegationSettings = { cwd: state.cwd, runner: stepBackend, model: backendModel,
|