@orion-agents/orion-code 0.3.1 → 0.3.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/CHANGELOG.md +43 -0
- package/README.md +39 -28
- package/README.zh-CN.md +32 -25
- package/dist/commands/model-command-handlers.d.ts.map +1 -1
- package/dist/commands/model-command-handlers.js +59 -14
- package/dist/commands/model-command-handlers.js.map +1 -1
- package/dist/commands/types.d.ts +6 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/workflow-command-handlers.d.ts +1 -1
- package/dist/commands/workflow-command-handlers.d.ts.map +1 -1
- package/dist/commands/workflow-command-handlers.js +42 -3
- package/dist/commands/workflow-command-handlers.js.map +1 -1
- package/dist/commands/workflow-commands.d.ts.map +1 -1
- package/dist/commands/workflow-commands.js +2 -2
- package/dist/commands/workflow-commands.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +16 -2
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +115 -6
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +3 -0
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/agent-runtime-runner.d.ts +8 -0
- package/dist/runtime/agent-runtime-runner.d.ts.map +1 -1
- package/dist/runtime/orion-session-runner.d.ts +4 -0
- package/dist/runtime/orion-session-runner.d.ts.map +1 -1
- package/dist/runtime/orion-session-runner.js +15 -0
- package/dist/runtime/orion-session-runner.js.map +1 -1
- package/dist/runtime/plan-review.d.ts +38 -0
- package/dist/runtime/plan-review.d.ts.map +1 -0
- package/dist/runtime/plan-review.js +173 -0
- package/dist/runtime/plan-review.js.map +1 -0
- package/dist/runtime/product-bootstrap.d.ts.map +1 -1
- package/dist/runtime/product-bootstrap.js +28 -6
- package/dist/runtime/product-bootstrap.js.map +1 -1
- package/dist/runtime/product-orion-runtime.d.ts.map +1 -1
- package/dist/runtime/product-orion-runtime.js +14 -35
- package/dist/runtime/product-orion-runtime.js.map +1 -1
- package/dist/runtime/protocol/runtime-protocol-v1.d.ts +73 -0
- package/dist/runtime/protocol/runtime-protocol-v1.d.ts.map +1 -1
- package/dist/runtime/protocol/runtime-protocol-v1.js +37 -0
- package/dist/runtime/protocol/runtime-protocol-v1.js.map +1 -1
- package/dist/runtime/release-receipts.d.ts +5 -3
- package/dist/runtime/release-receipts.d.ts.map +1 -1
- package/dist/runtime/release-receipts.js +42 -1
- package/dist/runtime/release-receipts.js.map +1 -1
- package/dist/runtime/session-composer-control.d.ts +136 -0
- package/dist/runtime/session-composer-control.d.ts.map +1 -0
- package/dist/runtime/session-composer-control.js +532 -0
- package/dist/runtime/session-composer-control.js.map +1 -0
- package/dist/runtime/thread-projection.d.ts +17 -0
- package/dist/runtime/thread-projection.d.ts.map +1 -1
- package/dist/runtime/thread-projection.js +87 -0
- package/dist/runtime/thread-projection.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +6 -1
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/services/session-storage.d.ts +36 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +77 -1
- package/dist/services/session-storage.js.map +1 -1
- package/dist/web/event-hub.js +10 -0
- package/dist/web/event-hub.js.map +1 -1
- package/dist/web/file-read-service.d.ts +9 -0
- package/dist/web/file-read-service.d.ts.map +1 -1
- package/dist/web/file-read-service.js +17 -7
- package/dist/web/file-read-service.js.map +1 -1
- package/dist/web/git-read-model-service.d.ts +8 -0
- package/dist/web/git-read-model-service.d.ts.map +1 -1
- package/dist/web/git-read-model-service.js +28 -2
- package/dist/web/git-read-model-service.js.map +1 -1
- package/dist/web/protocol.d.ts +142 -0
- package/dist/web/protocol.d.ts.map +1 -1
- package/dist/web/protocol.js +207 -0
- package/dist/web/protocol.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +45 -8
- package/dist/web/server.js.map +1 -1
- package/dist/web/terminal-manager.d.ts +1 -0
- package/dist/web/terminal-manager.d.ts.map +1 -1
- package/dist/web/terminal-manager.js +41 -9
- package/dist/web/terminal-manager.js.map +1 -1
- package/dist/web/workbench-controller.d.ts +26 -9
- package/dist/web/workbench-controller.d.ts.map +1 -1
- package/dist/web/workbench-controller.js +545 -29
- package/dist/web/workbench-controller.js.map +1 -1
- package/dist/web-client/assets/{DiffViewer-DpKoD07C.js → DiffViewer-eaQNsnCh.js} +1 -1
- package/dist/web-client/assets/FilesPanel-DYs1MhoM.js +2 -0
- package/dist/web-client/assets/{GitPanel-D20StcdG.js → GitPanel-CnU4XVr7.js} +1 -1
- package/dist/web-client/assets/{ReviewPanel-3MVWbMQV.js → ReviewPanel-BDxG670T.js} +1 -1
- package/dist/web-client/assets/{TerminalPanel-BLQ592Fb.js → TerminalPanel-Q5mSSsoK.js} +15 -13
- package/dist/web-client/assets/index-BWV5sIvw.css +1 -0
- package/dist/web-client/assets/index-VVUKj6FE.js +17 -0
- package/dist/web-client/index.html +2 -2
- package/docs/architecture/agent-mode-permission-contract.md +87 -0
- package/docs/architecture/v0.3.1-web-api.yaml +53 -5
- package/docs/architecture/v0.3.2-web-api.yaml +3029 -0
- package/docs/migration/v0.3.1-to-v0.3.2.md +94 -0
- package/docs/plan/v0.3.1-web-workbench-professional-shell-plan.md +1 -1
- package/docs/plan/v0.3.2-web-workbench-layout-and-composer-plan.md +869 -0
- package/docs/readme.md +12 -2
- package/docs/test/v0.3.1-web-workbench-e2e-plan.md +1 -1
- package/docs/test/v0.3.2-web-workbench-e2e-plan.md +100 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +6 -1
- package/dist/web-client/assets/FilesPanel-BkoLmgjR.js +0 -2
- package/dist/web-client/assets/index-BkIDl_xk.js +0 -16
- package/dist/web-client/assets/index-DXMpnWE8.css +0 -1
|
@@ -11,11 +11,14 @@ const durable_tool_receipt_reader_1 = require("../runtime/durable-tool-receipt-r
|
|
|
11
11
|
const mcp_1 = require("../runtime/mcp");
|
|
12
12
|
const product_bootstrap_1 = require("../runtime/product-bootstrap");
|
|
13
13
|
const canonical_1 = require("../runtime/protocol/canonical");
|
|
14
|
+
const plan_review_1 = require("../runtime/plan-review");
|
|
14
15
|
const thread_session_index_1 = require("../runtime/thread-session-index");
|
|
15
16
|
const thread_session_view_1 = require("../runtime/thread-session-view");
|
|
16
17
|
const tool_detail_repository_1 = require("../runtime/tool-detail-repository");
|
|
17
18
|
const turn_commit_1 = require("../runtime/turn-commit");
|
|
19
|
+
const session_composer_control_1 = require("../runtime/session-composer-control");
|
|
18
20
|
const global_config_1 = require("../services/global-config");
|
|
21
|
+
const redaction_1 = require("../services/redaction");
|
|
19
22
|
const settings_coordinator_1 = require("../services/settings-coordinator");
|
|
20
23
|
const session_storage_1 = require("../services/session-storage");
|
|
21
24
|
const workspace_registry_1 = require("../services/workspace-registry");
|
|
@@ -30,6 +33,8 @@ var errors_2 = require("./errors");
|
|
|
30
33
|
Object.defineProperty(exports, "WebWorkbenchError", { enumerable: true, get: function () { return errors_2.WebWorkbenchError; } });
|
|
31
34
|
const SESSION_VIEW_CACHE_BYTES = 16 * 1024 * 1024;
|
|
32
35
|
const MAX_MUTATION_RESULTS = 4096;
|
|
36
|
+
const MAX_CONTEXT_REFERENCE_BYTES = 64 * 1024;
|
|
37
|
+
const MAX_CONTEXT_MANIFEST_BYTES = 256 * 1024;
|
|
33
38
|
/** Owns the sole runtime/controller pair exposed through the local Web host. */
|
|
34
39
|
class WebWorkbenchController {
|
|
35
40
|
constructor(options) {
|
|
@@ -38,6 +43,8 @@ class WebWorkbenchController {
|
|
|
38
43
|
this.sessionViewBytes = 0;
|
|
39
44
|
this.toolDetails = new tool_detail_repository_1.FileToolDetailRepository();
|
|
40
45
|
this.contextRevisionValue = (0, crypto_1.randomUUID)();
|
|
46
|
+
this.composerRevisionValue = (0, crypto_1.randomUUID)();
|
|
47
|
+
this.suppressComposerEdges = 0;
|
|
41
48
|
this.suppressContextEdges = 0;
|
|
42
49
|
this.latestStatus = 'Ready';
|
|
43
50
|
this.sessionTransition = false;
|
|
@@ -131,18 +138,15 @@ class WebWorkbenchController {
|
|
|
131
138
|
throw mapSettingsError(error);
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
|
-
listWorkspaces() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return Object.freeze(this.workspaceRegistry.list().map(entry => {
|
|
141
|
+
listWorkspaces(context) {
|
|
142
|
+
if (context)
|
|
143
|
+
this.assertContextGuard(context);
|
|
144
|
+
// Session metadata is canonicalized before it enters the catalog. The
|
|
145
|
+
// summary read model deliberately avoids sorting Sessions or re-running
|
|
146
|
+
// realpath/stat for every historical entry. Availability is checked once
|
|
147
|
+
// per registered Workspace below.
|
|
148
|
+
const counts = (0, session_storage_1.countSessionsByProject)();
|
|
149
|
+
const result = Object.freeze(this.workspaceRegistry.list().map(entry => {
|
|
146
150
|
let available = false;
|
|
147
151
|
try {
|
|
148
152
|
available = canonicalDirectory(entry.canonicalPath) === entry.canonicalPath;
|
|
@@ -161,8 +165,13 @@ class WebWorkbenchController {
|
|
|
161
165
|
...(entry.pinnedOrder !== undefined ? { pinnedOrder: entry.pinnedOrder } : {}),
|
|
162
166
|
});
|
|
163
167
|
}));
|
|
168
|
+
if (context)
|
|
169
|
+
this.assertContextGuard(context);
|
|
170
|
+
return result;
|
|
164
171
|
}
|
|
165
|
-
listWorkspaceSessions(workspaceId) {
|
|
172
|
+
listWorkspaceSessions(workspaceId, context) {
|
|
173
|
+
if (context)
|
|
174
|
+
this.assertContextGuard(context);
|
|
166
175
|
const entry = this.workspaceRegistry.find(workspaceId);
|
|
167
176
|
if (!entry) {
|
|
168
177
|
throw new errors_1.WebWorkbenchError(404, 'Workspace was not found.', 'workspace_not_found');
|
|
@@ -173,9 +182,14 @@ class WebWorkbenchController {
|
|
|
173
182
|
catch {
|
|
174
183
|
throw new errors_1.WebWorkbenchError(409, 'Workspace is unavailable.', 'workspace_unavailable');
|
|
175
184
|
}
|
|
176
|
-
|
|
185
|
+
const result = Object.freeze((0, session_storage_1.listProjectSessions)(entry.canonicalPath).map(protocol_1.projectSessionSummary));
|
|
186
|
+
if (context)
|
|
187
|
+
this.assertContextGuard(context);
|
|
188
|
+
return result;
|
|
177
189
|
}
|
|
178
|
-
async workspaceProjectSummary(workspaceId) {
|
|
190
|
+
async workspaceProjectSummary(workspaceId, context) {
|
|
191
|
+
if (context)
|
|
192
|
+
this.assertContextGuard(context);
|
|
179
193
|
const entry = this.workspaceRegistry.find(workspaceId);
|
|
180
194
|
if (!entry) {
|
|
181
195
|
throw new errors_1.WebWorkbenchError(404, 'Workspace was not found.', 'workspace_not_found');
|
|
@@ -188,7 +202,7 @@ class WebWorkbenchController {
|
|
|
188
202
|
throw new errors_1.WebWorkbenchError(409, 'Workspace is unavailable.', 'workspace_unavailable');
|
|
189
203
|
}
|
|
190
204
|
const status = await new git_read_model_service_1.GitReadModelServiceV1(workspace).status({ pageSize: 2000 });
|
|
191
|
-
|
|
205
|
+
const result = Object.freeze({
|
|
192
206
|
workspaceId,
|
|
193
207
|
repositoryRevision: status.repositoryRevision,
|
|
194
208
|
isRepository: status.isRepository,
|
|
@@ -198,6 +212,9 @@ class WebWorkbenchController {
|
|
|
198
212
|
dirtyCount: status.totalFiles,
|
|
199
213
|
conflictCount: status.conflicted.length,
|
|
200
214
|
});
|
|
215
|
+
if (context)
|
|
216
|
+
this.assertContextGuard(context);
|
|
217
|
+
return result;
|
|
201
218
|
}
|
|
202
219
|
setWorkspacePinned(workspaceId, pinned, context) {
|
|
203
220
|
this.assertContextGuard(context);
|
|
@@ -385,15 +402,206 @@ class WebWorkbenchController {
|
|
|
385
402
|
pendingApprovals: Object.freeze(pendingPermissions),
|
|
386
403
|
goal,
|
|
387
404
|
plan,
|
|
405
|
+
composer: this.projectComposerState(session, active),
|
|
388
406
|
recoveryDiagnostics: indexedPage ? [] : (view?.diagnostics ?? []),
|
|
389
407
|
});
|
|
390
408
|
if (context)
|
|
391
409
|
this.assertContextGuard(context);
|
|
392
410
|
return snapshot;
|
|
393
411
|
}
|
|
394
|
-
|
|
412
|
+
composerState(sessionId, context) {
|
|
413
|
+
if (context)
|
|
414
|
+
this.assertContextGuard(context);
|
|
415
|
+
this.assertCommandSession(sessionId);
|
|
416
|
+
const session = requireSession(sessionId);
|
|
417
|
+
const state = this.projectComposerState(session, true);
|
|
418
|
+
if (context)
|
|
419
|
+
this.assertContextGuard(context);
|
|
420
|
+
return state;
|
|
421
|
+
}
|
|
422
|
+
projectComposerState(session, active) {
|
|
423
|
+
const state = this.projectComposerStateValue(session, active);
|
|
424
|
+
if (!active)
|
|
425
|
+
return state;
|
|
426
|
+
// Durable Thread and Store authority can advance before the debounced SSE
|
|
427
|
+
// observer runs. Keep every projected control state and its CAS token in
|
|
428
|
+
// the same synchronous boundary so the UI never receives new controls
|
|
429
|
+
// paired with an older revision.
|
|
430
|
+
const authorityDigest = composerAuthorityDigest(state);
|
|
431
|
+
if (this.composerAuthorityDigest === undefined) {
|
|
432
|
+
this.composerAuthorityDigest = authorityDigest;
|
|
433
|
+
return state;
|
|
434
|
+
}
|
|
435
|
+
if (authorityDigest === this.composerAuthorityDigest)
|
|
436
|
+
return state;
|
|
437
|
+
this.bumpComposerRevision();
|
|
438
|
+
this.composerAuthorityDigest = authorityDigest;
|
|
439
|
+
return Object.freeze({ ...state, controlRevision: this.composerRevisionValue });
|
|
440
|
+
}
|
|
441
|
+
projectComposerStateValue(session, active) {
|
|
442
|
+
const controls = this.runtimeValue.sessionComposerControls;
|
|
443
|
+
if (!controls) {
|
|
444
|
+
throw new errors_1.WebWorkbenchError(503, 'Session Composer controls are unavailable.', 'composer_controls_unavailable');
|
|
445
|
+
}
|
|
446
|
+
const runtime = controls.describeSession(session);
|
|
447
|
+
const queue = active
|
|
448
|
+
? this.controllerValue.getFollowupQueueSnapshot()
|
|
449
|
+
: { items: [], limit: 16 };
|
|
450
|
+
const projection = active && this.activeOrionRuntime?.sessionId === session.id
|
|
451
|
+
? this.activeOrionRuntime.runtime.thread.getProjection()
|
|
452
|
+
: undefined;
|
|
453
|
+
const review = projection?.planReview;
|
|
454
|
+
const workspace = this.activeWorkspaceEntry();
|
|
455
|
+
return Object.freeze({
|
|
456
|
+
apiVersion: protocol_1.WEB_API_VERSION,
|
|
457
|
+
workspaceId: workspace.id,
|
|
458
|
+
sessionId: session.id,
|
|
459
|
+
contextRevision: this.contextRevisionValue,
|
|
460
|
+
controlRevision: this.composerRevisionValue,
|
|
461
|
+
processing: active &&
|
|
462
|
+
(this.runtimeValue.store.getSnapshot().isProcessing || Boolean(projection?.activeTurnId)),
|
|
463
|
+
mode: active
|
|
464
|
+
? Object.freeze({ ...this.controllerValue.getAgentModeSnapshot() })
|
|
465
|
+
: Object.freeze({ baseMode: 'interactive', pendingBaseMode: null }),
|
|
466
|
+
model: runtime.model,
|
|
467
|
+
permission: runtime.permission,
|
|
468
|
+
contextUsage: runtime.contextUsage,
|
|
469
|
+
compactAvailable: active && this.controllerValue.canCompactContext(),
|
|
470
|
+
pending: runtime.pending,
|
|
471
|
+
lastError: runtime.lastError,
|
|
472
|
+
planReview: review
|
|
473
|
+
? Object.freeze({
|
|
474
|
+
planDigest: review.planDigest,
|
|
475
|
+
revision: review.revision,
|
|
476
|
+
status: review.status,
|
|
477
|
+
createdAt: review.createdAt,
|
|
478
|
+
createdModel: review.createdModel,
|
|
479
|
+
returnMode: review.returnMode,
|
|
480
|
+
...(review.resolvedAt === undefined ? {} : { resolvedAt: review.resolvedAt }),
|
|
481
|
+
})
|
|
482
|
+
: null,
|
|
483
|
+
queue: Object.freeze({
|
|
484
|
+
items: Object.freeze(queue.items.map(item => Object.freeze({ ...item }))),
|
|
485
|
+
limit: queue.limit,
|
|
486
|
+
}),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
modelCatalog(sessionId, context) {
|
|
490
|
+
if (context)
|
|
491
|
+
this.assertContextGuard(context);
|
|
492
|
+
this.assertCommandSession(sessionId);
|
|
493
|
+
const controls = this.runtimeValue.sessionComposerControls;
|
|
494
|
+
if (!controls) {
|
|
495
|
+
throw new errors_1.WebWorkbenchError(503, 'Session model catalog is unavailable.', 'composer_controls_unavailable');
|
|
496
|
+
}
|
|
497
|
+
const catalog = controls.catalog();
|
|
498
|
+
if (context)
|
|
499
|
+
this.assertContextGuard(context);
|
|
500
|
+
return catalog;
|
|
501
|
+
}
|
|
502
|
+
async applyComposerAction(input) {
|
|
503
|
+
this.assertContextGuard(input);
|
|
504
|
+
this.assertCommandSession(input.expectedSessionId);
|
|
505
|
+
const admittedState = this.composerState(input.expectedSessionId);
|
|
506
|
+
if (input.expectedControlRevision !== admittedState.controlRevision) {
|
|
507
|
+
throw new errors_1.WebWorkbenchError(409, 'Composer controls changed before the action was admitted.', 'composer_control_conflict');
|
|
508
|
+
}
|
|
509
|
+
const controls = this.runtimeValue.sessionComposerControls;
|
|
510
|
+
if (!controls) {
|
|
511
|
+
throw new errors_1.WebWorkbenchError(503, 'Session Composer controls are unavailable.', 'composer_controls_unavailable');
|
|
512
|
+
}
|
|
513
|
+
let outcome = 'applied';
|
|
514
|
+
let detail;
|
|
515
|
+
let modelReceipt;
|
|
516
|
+
let permissionReceipt;
|
|
517
|
+
let planReviewReceipt;
|
|
518
|
+
this.suppressComposerEdges += 1;
|
|
519
|
+
try {
|
|
520
|
+
switch (input.type) {
|
|
521
|
+
case 'set_agent_mode': {
|
|
522
|
+
const result = this.controllerValue.setAgentMode(input.mode);
|
|
523
|
+
outcome =
|
|
524
|
+
result.type === 'agent_mode_changed' && result.appliesFrom === 'next-logical-request'
|
|
525
|
+
? 'deferred'
|
|
526
|
+
: 'applied';
|
|
527
|
+
detail = JSON.stringify(result);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
case 'set_permission_override':
|
|
531
|
+
permissionReceipt = await controls.setPermissionOverride(input.value);
|
|
532
|
+
outcome = permissionReceipt.appliesFrom === 'immediate' ? 'applied' : 'deferred';
|
|
533
|
+
break;
|
|
534
|
+
case 'clear_permission_override':
|
|
535
|
+
permissionReceipt = await controls.setPermissionOverride(null);
|
|
536
|
+
outcome = permissionReceipt.appliesFrom === 'immediate' ? 'applied' : 'deferred';
|
|
537
|
+
break;
|
|
538
|
+
case 'select_model':
|
|
539
|
+
modelReceipt = await controls.selectModel({
|
|
540
|
+
modelId: input.modelId,
|
|
541
|
+
...(input.effort ? { effort: input.effort } : {}),
|
|
542
|
+
});
|
|
543
|
+
outcome = modelReceipt.appliesFrom === 'next-logical-request' ? 'deferred' : 'applied';
|
|
544
|
+
break;
|
|
545
|
+
case 'compact_context': {
|
|
546
|
+
const result = await this.controllerValue.compactContext();
|
|
547
|
+
if (result.status === 'rejected') {
|
|
548
|
+
throw new errors_1.WebWorkbenchError(409, 'Context compact was rejected.', 'runtime_busy');
|
|
549
|
+
}
|
|
550
|
+
detail = JSON.stringify(result);
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
case 'edit_queue_item':
|
|
554
|
+
detail = JSON.stringify(this.controllerValue.editFollowupQueueItem(input.itemId, input.expectedItemRevision, input.text));
|
|
555
|
+
break;
|
|
556
|
+
case 'move_queue_item':
|
|
557
|
+
detail = JSON.stringify(this.controllerValue.moveFollowupQueueItem(input.itemId, input.expectedItemRevision, input.targetIndex));
|
|
558
|
+
break;
|
|
559
|
+
case 'remove_queue_item':
|
|
560
|
+
detail = JSON.stringify(this.controllerValue.removeFollowupQueueItem(input.itemId, input.expectedItemRevision));
|
|
561
|
+
break;
|
|
562
|
+
case 'review_plan': {
|
|
563
|
+
planReviewReceipt = await this.controllerValue.reviewPlan({
|
|
564
|
+
planDigest: input.planDigest,
|
|
565
|
+
action: input.action,
|
|
566
|
+
...(input.feedback ? { feedback: input.feedback } : {}),
|
|
567
|
+
});
|
|
568
|
+
if (input.action === 'approve')
|
|
569
|
+
this.controllerValue.setAgentMode('interactive');
|
|
570
|
+
else if (input.action === 'continue')
|
|
571
|
+
this.controllerValue.setAgentMode('plan');
|
|
572
|
+
detail = JSON.stringify(planReviewReceipt.admission);
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
throw mapComposerControlError(error);
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
this.suppressComposerEdges -= 1;
|
|
582
|
+
}
|
|
583
|
+
this.bumpComposerRevision();
|
|
584
|
+
const state = this.projectComposerStateValue(requireSession(input.expectedSessionId), true);
|
|
585
|
+
this.composerAuthorityDigest = composerAuthorityDigest(state);
|
|
586
|
+
this.eventHub.emit({ type: 'composer_state_changed', state }, true, {
|
|
587
|
+
sessionId: input.expectedSessionId,
|
|
588
|
+
});
|
|
589
|
+
return Object.freeze({
|
|
590
|
+
requestId: input.requestId,
|
|
591
|
+
outcome,
|
|
592
|
+
controlRevision: this.composerRevisionValue,
|
|
593
|
+
state,
|
|
594
|
+
...(modelReceipt ? { modelReceipt } : {}),
|
|
595
|
+
...(permissionReceipt ? { permissionReceipt } : {}),
|
|
596
|
+
...(planReviewReceipt ? { planReviewReceipt } : {}),
|
|
597
|
+
...(detail ? { detail } : {}),
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
async skills(context) {
|
|
601
|
+
if (context)
|
|
602
|
+
this.assertContextGuard(context);
|
|
395
603
|
const descriptors = (await this.runtimeValue.inspectSkills?.()) ?? [];
|
|
396
|
-
|
|
604
|
+
const result = Object.freeze(descriptors.map(descriptor => Object.freeze({
|
|
397
605
|
id: descriptor.id,
|
|
398
606
|
name: descriptor.name,
|
|
399
607
|
description: descriptor.description,
|
|
@@ -404,8 +612,13 @@ class WebWorkbenchController {
|
|
|
404
612
|
requestedCapabilities: Object.freeze([...descriptor.requestedCapabilities]),
|
|
405
613
|
digest: descriptor.digest,
|
|
406
614
|
})));
|
|
615
|
+
if (context)
|
|
616
|
+
this.assertContextGuard(context);
|
|
617
|
+
return result;
|
|
407
618
|
}
|
|
408
|
-
mcp() {
|
|
619
|
+
mcp(context) {
|
|
620
|
+
if (context)
|
|
621
|
+
this.assertContextGuard(context);
|
|
409
622
|
const descriptors = this.runtimeValue.inspectMcp?.() ?? [];
|
|
410
623
|
let runtimeServers = new Map();
|
|
411
624
|
try {
|
|
@@ -417,7 +630,7 @@ class WebWorkbenchController {
|
|
|
417
630
|
catch {
|
|
418
631
|
// A concurrent session transition may close the previous diagnostics graph.
|
|
419
632
|
}
|
|
420
|
-
|
|
633
|
+
const result = Object.freeze(descriptors.map(descriptor => {
|
|
421
634
|
const runtime = runtimeServers.get(descriptor.id);
|
|
422
635
|
return Object.freeze({
|
|
423
636
|
id: descriptor.id,
|
|
@@ -435,20 +648,33 @@ class WebWorkbenchController {
|
|
|
435
648
|
: {}),
|
|
436
649
|
});
|
|
437
650
|
}));
|
|
651
|
+
if (context)
|
|
652
|
+
this.assertContextGuard(context);
|
|
653
|
+
return result;
|
|
438
654
|
}
|
|
439
|
-
async listToolDetails() {
|
|
440
|
-
|
|
655
|
+
async listToolDetails(context) {
|
|
656
|
+
if (context)
|
|
657
|
+
this.assertContextGuard(context);
|
|
658
|
+
const result = Object.freeze(await this.toolDetails.list(this.workspaceValue));
|
|
659
|
+
if (context)
|
|
660
|
+
this.assertContextGuard(context);
|
|
661
|
+
return result;
|
|
441
662
|
}
|
|
442
|
-
async readToolDetail(callId, offsetBytes, limitBytes) {
|
|
663
|
+
async readToolDetail(callId, offsetBytes, limitBytes, context) {
|
|
664
|
+
if (context)
|
|
665
|
+
this.assertContextGuard(context);
|
|
443
666
|
const entry = (await this.toolDetails.list(this.workspaceValue)).find(detail => detail.callId === callId || detail.artifactId === callId);
|
|
444
667
|
if (!entry?.artifactId)
|
|
445
668
|
throw new errors_1.WebWorkbenchError(404, 'Tool detail was not found.');
|
|
446
|
-
|
|
669
|
+
const result = await this.toolDetails.read({
|
|
447
670
|
callId: entry.callId,
|
|
448
671
|
sequence: entry.sequence,
|
|
449
672
|
artifactId: entry.artifactId,
|
|
450
673
|
outputBytes: entry.outputBytes,
|
|
451
674
|
}, { offsetBytes, limitBytes }, this.workspaceValue);
|
|
675
|
+
if (context)
|
|
676
|
+
this.assertContextGuard(context);
|
|
677
|
+
return result;
|
|
452
678
|
}
|
|
453
679
|
listFiles(context, input) {
|
|
454
680
|
this.assertContextGuard(context);
|
|
@@ -648,16 +874,179 @@ class WebWorkbenchController {
|
|
|
648
874
|
}
|
|
649
875
|
dispatch(raw) {
|
|
650
876
|
const command = (0, protocol_1.parseWebCommand)(raw);
|
|
651
|
-
return this.executeMutation(command.requestId, 'command', command, () => {
|
|
877
|
+
return this.executeMutation(command.requestId, 'command', command, async () => {
|
|
878
|
+
this.assertCommandSession(command.expectedSessionId);
|
|
879
|
+
const resolved = await this.resolveCommandContext(command);
|
|
652
880
|
this.assertCommandSession(command.expectedSessionId);
|
|
653
|
-
const
|
|
881
|
+
const runtimeInput = resolved && command.type === 'queue_followup'
|
|
882
|
+
? {
|
|
883
|
+
type: 'queue_followup',
|
|
884
|
+
text: command.text,
|
|
885
|
+
resolvedText: resolved.text,
|
|
886
|
+
source: 'programmatic',
|
|
887
|
+
}
|
|
888
|
+
: (0, protocol_1.toAgentRuntimeInput)({ ...command, ...(resolved ? { text: resolved.text } : {}) });
|
|
889
|
+
const runtimeResult = this.controllerValue.handle(runtimeInput);
|
|
654
890
|
return Object.freeze({
|
|
655
891
|
requestId: command.requestId,
|
|
656
892
|
result: runtimeResult.type,
|
|
657
893
|
detail: JSON.stringify(runtimeResult),
|
|
894
|
+
...(resolved ? { contextReceipt: resolved.receipt } : {}),
|
|
658
895
|
});
|
|
659
896
|
});
|
|
660
897
|
}
|
|
898
|
+
async resolveCommandContext(command) {
|
|
899
|
+
const references = command.contextReferences ?? [];
|
|
900
|
+
if (references.length === 0)
|
|
901
|
+
return undefined;
|
|
902
|
+
if (command.type !== 'submit' && command.type !== 'queue_followup') {
|
|
903
|
+
throw new errors_1.WebWorkbenchError(400, 'Context references are valid only for submitted or queued messages.', 'context_reference_invalid');
|
|
904
|
+
}
|
|
905
|
+
const resolved = [];
|
|
906
|
+
let totalBytes = 0;
|
|
907
|
+
for (const reference of references) {
|
|
908
|
+
let entry;
|
|
909
|
+
try {
|
|
910
|
+
entry = await this.resolveContextReference(reference);
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
throw mapContextReferenceError(error);
|
|
914
|
+
}
|
|
915
|
+
const bytes = Buffer.byteLength(JSON.stringify(entry), 'utf8');
|
|
916
|
+
if (bytes > MAX_CONTEXT_REFERENCE_BYTES) {
|
|
917
|
+
throw new errors_1.WebWorkbenchError(413, 'A Context reference exceeds the per-reference byte budget.', 'context_reference_too_large');
|
|
918
|
+
}
|
|
919
|
+
totalBytes += bytes;
|
|
920
|
+
if (totalBytes > MAX_CONTEXT_MANIFEST_BYTES) {
|
|
921
|
+
throw new errors_1.WebWorkbenchError(413, 'Context references exceed the request byte budget.', 'context_reference_too_large');
|
|
922
|
+
}
|
|
923
|
+
resolved.push(Object.freeze(entry));
|
|
924
|
+
}
|
|
925
|
+
const manifestContent = Object.freeze({
|
|
926
|
+
version: 1,
|
|
927
|
+
references: Object.freeze(resolved),
|
|
928
|
+
});
|
|
929
|
+
const manifestDigest = (0, canonical_1.digestRuntimeValue)(manifestContent);
|
|
930
|
+
const manifest = Object.freeze({ ...manifestContent, manifestDigest });
|
|
931
|
+
return Object.freeze({
|
|
932
|
+
text: [
|
|
933
|
+
command.text.trim(),
|
|
934
|
+
'',
|
|
935
|
+
'[Orion Context Manifest V1]',
|
|
936
|
+
JSON.stringify(manifest),
|
|
937
|
+
].join('\n'),
|
|
938
|
+
receipt: Object.freeze({
|
|
939
|
+
manifestDigest,
|
|
940
|
+
referenceCount: resolved.length,
|
|
941
|
+
totalBytes,
|
|
942
|
+
}),
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
async resolveContextReference(reference) {
|
|
946
|
+
switch (reference.kind) {
|
|
947
|
+
case 'file': {
|
|
948
|
+
const page = this.fileService.readContent({
|
|
949
|
+
fileId: reference.id,
|
|
950
|
+
limitBytes: MAX_CONTEXT_REFERENCE_BYTES,
|
|
951
|
+
});
|
|
952
|
+
if (page.revision !== reference.revision) {
|
|
953
|
+
throw new errors_1.WebWorkbenchError(409, 'Referenced file changed.', 'context_reference_stale');
|
|
954
|
+
}
|
|
955
|
+
if (page.binary || page.content === undefined) {
|
|
956
|
+
throw new errors_1.WebWorkbenchError(403, 'Binary files cannot be added to model Context.', 'context_reference_forbidden');
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
kind: 'file',
|
|
960
|
+
id: reference.id,
|
|
961
|
+
label: this.sanitizeContextText(page.name),
|
|
962
|
+
revision: page.revision,
|
|
963
|
+
content: this.sanitizeContextText(page.content),
|
|
964
|
+
truncated: page.nextCursor !== null,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
case 'folder': {
|
|
968
|
+
const page = this.fileService.list({ parentId: reference.id, pageSize: 100 });
|
|
969
|
+
if (page.revision !== reference.revision) {
|
|
970
|
+
throw new errors_1.WebWorkbenchError(409, 'Referenced folder changed.', 'context_reference_stale');
|
|
971
|
+
}
|
|
972
|
+
return {
|
|
973
|
+
kind: 'folder',
|
|
974
|
+
id: reference.id,
|
|
975
|
+
label: this.sanitizeContextText(reference.label),
|
|
976
|
+
revision: page.revision,
|
|
977
|
+
entries: page.items
|
|
978
|
+
.filter(item => !item.sensitive)
|
|
979
|
+
.map(item => ({
|
|
980
|
+
name: this.sanitizeContextText(item.name),
|
|
981
|
+
kind: item.kind,
|
|
982
|
+
sizeBytes: item.sizeBytes ?? null,
|
|
983
|
+
})),
|
|
984
|
+
truncated: page.nextCursor !== null,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
case 'review': {
|
|
988
|
+
const snapshot = await this.reviewService.snapshot();
|
|
989
|
+
if (snapshot.repositoryRevision !== reference.gitRevision) {
|
|
990
|
+
throw new errors_1.WebWorkbenchError(409, 'Referenced Review changed.', 'context_reference_stale');
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
kind: 'review',
|
|
994
|
+
id: reference.id,
|
|
995
|
+
revision: snapshot.repositoryRevision,
|
|
996
|
+
clean: snapshot.clean,
|
|
997
|
+
files: snapshot.changedFiles.slice(0, 100).map(file => ({
|
|
998
|
+
id: file.fileId,
|
|
999
|
+
path: this.sanitizeContextText(file.path),
|
|
1000
|
+
indexStatus: file.indexStatus,
|
|
1001
|
+
worktreeStatus: file.worktreeStatus,
|
|
1002
|
+
})),
|
|
1003
|
+
truncated: snapshot.truncated || snapshot.changedFiles.length > 100,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
case 'session': {
|
|
1007
|
+
const session = requireSession(reference.id);
|
|
1008
|
+
if (canonicalDirectory(session.projectPath) !== this.workspaceValue) {
|
|
1009
|
+
throw new errors_1.WebWorkbenchError(403, 'Referenced Session belongs to another Workspace.', 'context_reference_forbidden');
|
|
1010
|
+
}
|
|
1011
|
+
const summary = (0, protocol_1.projectSessionSummary)(session);
|
|
1012
|
+
if (summary.contextDigest !== reference.digest) {
|
|
1013
|
+
throw new errors_1.WebWorkbenchError(409, 'Referenced Session changed.', 'context_reference_stale');
|
|
1014
|
+
}
|
|
1015
|
+
const page = (0, thread_session_view_1.loadThreadSessionSnapshotPageV1)(this.workspaceValue, session.id, undefined, 8);
|
|
1016
|
+
return {
|
|
1017
|
+
kind: 'session',
|
|
1018
|
+
id: session.id,
|
|
1019
|
+
label: this.sanitizeContextText(session.name?.trim() || `Session ${session.id.slice(0, 8)}`),
|
|
1020
|
+
digest: summary.contextDigest,
|
|
1021
|
+
messages: (page?.transcript.items ?? []).map(message => ({
|
|
1022
|
+
role: message.role,
|
|
1023
|
+
content: this.sanitizeContextText(message.content),
|
|
1024
|
+
})),
|
|
1025
|
+
truncated: Boolean(page?.transcript.nextCursor),
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
case 'skill': {
|
|
1029
|
+
const descriptor = ((await this.runtimeValue.inspectSkills?.()) ?? []).find(skill => skill.id === reference.id);
|
|
1030
|
+
if (!descriptor || descriptor.digest !== reference.digest) {
|
|
1031
|
+
throw new errors_1.WebWorkbenchError(409, 'Referenced Skill changed.', 'context_reference_stale');
|
|
1032
|
+
}
|
|
1033
|
+
if (!descriptor.userInvocable) {
|
|
1034
|
+
throw new errors_1.WebWorkbenchError(403, 'Referenced Skill is not user-invocable.', 'context_reference_forbidden');
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
kind: 'skill',
|
|
1038
|
+
id: descriptor.id,
|
|
1039
|
+
label: this.sanitizeContextText(descriptor.name),
|
|
1040
|
+
digest: descriptor.digest,
|
|
1041
|
+
invocation: `$${descriptor.id}`,
|
|
1042
|
+
description: this.sanitizeContextText(descriptor.description),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
sanitizeContextText(value) {
|
|
1048
|
+
return (0, redaction_1.redactTraceText)(value).split(this.workspaceValue).join('<workspace>');
|
|
1049
|
+
}
|
|
661
1050
|
async updateSettings(input) {
|
|
662
1051
|
this.assertSettingsAvailable();
|
|
663
1052
|
const update = this.runtimeValue.updateSettings;
|
|
@@ -716,7 +1105,9 @@ class WebWorkbenchController {
|
|
|
716
1105
|
this.mutationResults.set(requestId, { fingerprint, result });
|
|
717
1106
|
return result;
|
|
718
1107
|
}
|
|
719
|
-
async diagnostics() {
|
|
1108
|
+
async diagnostics(context) {
|
|
1109
|
+
if (context)
|
|
1110
|
+
this.assertContextGuard(context);
|
|
720
1111
|
const state = this.runtimeValue.store.getSnapshot();
|
|
721
1112
|
const mcp = (0, mcp_1.loadFirstPartyMcpConfigurationV1)();
|
|
722
1113
|
const activeSessionId = this.runtimeValue.getSession()?.id ?? null;
|
|
@@ -725,7 +1116,7 @@ class WebWorkbenchController {
|
|
|
725
1116
|
const harness = activeSessionId
|
|
726
1117
|
? ((await this.runtimeValue.getHarnessDiagnostics?.()) ?? null)
|
|
727
1118
|
: null;
|
|
728
|
-
|
|
1119
|
+
const result = {
|
|
729
1120
|
workspace: this.workspaceValue,
|
|
730
1121
|
activeSessionId,
|
|
731
1122
|
configured: this.runtimeValue.isConfigured,
|
|
@@ -743,7 +1134,14 @@ class WebWorkbenchController {
|
|
|
743
1134
|
mcp: { servers: mcpServerIds(mcp) },
|
|
744
1135
|
harness,
|
|
745
1136
|
eventStream: this.eventHub.snapshot(),
|
|
1137
|
+
performance: {
|
|
1138
|
+
files: this.fileService.performanceCounters(),
|
|
1139
|
+
git: this.gitService.performanceCounters(),
|
|
1140
|
+
},
|
|
746
1141
|
};
|
|
1142
|
+
if (context)
|
|
1143
|
+
this.assertContextGuard(context);
|
|
1144
|
+
return result;
|
|
747
1145
|
}
|
|
748
1146
|
async shutdown() {
|
|
749
1147
|
if (this.closed)
|
|
@@ -752,11 +1150,13 @@ class WebWorkbenchController {
|
|
|
752
1150
|
await this.controllerValue.stopActiveTurn();
|
|
753
1151
|
await this.controllerValue.waitForIdle();
|
|
754
1152
|
this.closeWorkspaceWatchers();
|
|
1153
|
+
this.closeComposerObserver();
|
|
755
1154
|
await this.terminalManager.shutdown();
|
|
756
1155
|
await this.runtimeValue.shutdown();
|
|
757
1156
|
this.eventHub.close();
|
|
758
1157
|
}
|
|
759
1158
|
async installRuntime(workspace, publishState = true) {
|
|
1159
|
+
this.closeComposerObserver();
|
|
760
1160
|
const runtime = await this.createRuntime(workspace);
|
|
761
1161
|
this.closeWorkspaceWatchers();
|
|
762
1162
|
this.activeOrionRuntime = undefined;
|
|
@@ -775,6 +1175,21 @@ class WebWorkbenchController {
|
|
|
775
1175
|
if (this.suppressContextEdges > 0)
|
|
776
1176
|
return undefined;
|
|
777
1177
|
const activeSessionId = this.runtimeValue.getSession()?.id;
|
|
1178
|
+
if (event.type === 'followup_queue_changed') {
|
|
1179
|
+
// The queue is part of the Composer CAS authority. Publishing the
|
|
1180
|
+
// Runtime queue event on its own lets the browser observe new items
|
|
1181
|
+
// with an older controlRevision and guarantees a 409 on its next
|
|
1182
|
+
// edit/move/remove action. Project one complete Composer state in
|
|
1183
|
+
// this same synchronous boundary instead. Composer actions suppress
|
|
1184
|
+
// their intermediate queue edge and publish their final state below.
|
|
1185
|
+
if (activeSessionId && this.suppressComposerEdges === 0) {
|
|
1186
|
+
const state = this.composerState(activeSessionId);
|
|
1187
|
+
this.eventHub.emit({ type: 'composer_state_changed', state }, true, {
|
|
1188
|
+
sessionId: activeSessionId,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return undefined;
|
|
1192
|
+
}
|
|
778
1193
|
const activeThread = activeSessionId && this.activeOrionRuntime?.sessionId === activeSessionId
|
|
779
1194
|
? this.activeOrionRuntime.runtime.thread.getProjection().threadId
|
|
780
1195
|
: undefined;
|
|
@@ -806,6 +1221,10 @@ class WebWorkbenchController {
|
|
|
806
1221
|
suppressAbortNotice: true,
|
|
807
1222
|
},
|
|
808
1223
|
});
|
|
1224
|
+
this.composerRevisionValue = (0, crypto_1.randomUUID)();
|
|
1225
|
+
this.composerAuthorityDigest = undefined;
|
|
1226
|
+
this.composerStoreUnsubscribe = runtime.store.subscribe(() => this.scheduleComposerStateChanged());
|
|
1227
|
+
this.composerControlsUnsubscribe = runtime.sessionComposerControls?.subscribe(() => this.scheduleComposerStateChanged());
|
|
809
1228
|
this.installWorkspaceWatchers();
|
|
810
1229
|
if (publishState)
|
|
811
1230
|
this.emitState(false);
|
|
@@ -815,13 +1234,27 @@ class WebWorkbenchController {
|
|
|
815
1234
|
return;
|
|
816
1235
|
if (advanceRevision)
|
|
817
1236
|
this.contextRevisionValue = (0, crypto_1.randomUUID)();
|
|
1237
|
+
if (advanceRevision)
|
|
1238
|
+
this.composerRevisionValue = (0, crypto_1.randomUUID)();
|
|
818
1239
|
const workspace = this.activeWorkspaceEntry();
|
|
1240
|
+
const sessionId = this.runtimeValue.getSession()?.id ?? null;
|
|
1241
|
+
if (sessionId) {
|
|
1242
|
+
try {
|
|
1243
|
+
this.composerAuthorityDigest = composerAuthorityDigest(this.projectComposerStateValue(requireSession(sessionId), true));
|
|
1244
|
+
}
|
|
1245
|
+
catch {
|
|
1246
|
+
this.composerAuthorityDigest = undefined;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
this.composerAuthorityDigest = undefined;
|
|
1251
|
+
}
|
|
819
1252
|
this.eventHub.emit({
|
|
820
1253
|
type: 'workbench_state',
|
|
821
1254
|
contextRevision: this.contextRevisionValue,
|
|
822
1255
|
workspaceId: workspace.id,
|
|
823
1256
|
workspace: this.workspaceValue,
|
|
824
|
-
activeSessionId:
|
|
1257
|
+
activeSessionId: sessionId,
|
|
825
1258
|
});
|
|
826
1259
|
}
|
|
827
1260
|
emitWorkspaceResourceInvalidation(reason) {
|
|
@@ -848,6 +1281,42 @@ class WebWorkbenchController {
|
|
|
848
1281
|
}, 120);
|
|
849
1282
|
this.resourceInvalidationTimer.unref();
|
|
850
1283
|
}
|
|
1284
|
+
bumpComposerRevision() {
|
|
1285
|
+
this.composerRevisionValue = (0, crypto_1.randomUUID)();
|
|
1286
|
+
}
|
|
1287
|
+
scheduleComposerStateChanged() {
|
|
1288
|
+
if (this.closed ||
|
|
1289
|
+
this.suppressComposerEdges > 0 ||
|
|
1290
|
+
this.suppressContextEdges > 0 ||
|
|
1291
|
+
!this.runtimeValue.getSession()) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (this.composerChangeTimer)
|
|
1295
|
+
clearTimeout(this.composerChangeTimer);
|
|
1296
|
+
this.composerChangeTimer = setTimeout(() => {
|
|
1297
|
+
this.composerChangeTimer = undefined;
|
|
1298
|
+
const sessionId = this.runtimeValue.getSession()?.id;
|
|
1299
|
+
if (!sessionId || this.closed || this.suppressComposerEdges > 0)
|
|
1300
|
+
return;
|
|
1301
|
+
try {
|
|
1302
|
+
const state = this.composerState(sessionId);
|
|
1303
|
+
this.eventHub.emit({ type: 'composer_state_changed', state }, true, { sessionId });
|
|
1304
|
+
}
|
|
1305
|
+
catch {
|
|
1306
|
+
// A concurrent Context transition publishes a fresh baseline instead.
|
|
1307
|
+
}
|
|
1308
|
+
}, 100);
|
|
1309
|
+
this.composerChangeTimer.unref();
|
|
1310
|
+
}
|
|
1311
|
+
closeComposerObserver() {
|
|
1312
|
+
this.composerStoreUnsubscribe?.();
|
|
1313
|
+
this.composerStoreUnsubscribe = undefined;
|
|
1314
|
+
this.composerControlsUnsubscribe?.();
|
|
1315
|
+
this.composerControlsUnsubscribe = undefined;
|
|
1316
|
+
if (this.composerChangeTimer)
|
|
1317
|
+
clearTimeout(this.composerChangeTimer);
|
|
1318
|
+
this.composerChangeTimer = undefined;
|
|
1319
|
+
}
|
|
851
1320
|
installWorkspaceWatchers() {
|
|
852
1321
|
const watchPaths = [this.workspaceValue, (0, path_1.resolve)(this.workspaceValue, '.git')].filter(path => (0, fs_1.existsSync)(path));
|
|
853
1322
|
for (const path of watchPaths) {
|
|
@@ -1071,6 +1540,53 @@ function mapSettingsError(error) {
|
|
|
1071
1540
|
}
|
|
1072
1541
|
return new errors_1.WebWorkbenchError(503, 'The product Settings coordinator is unavailable.', 'settings_document_unavailable');
|
|
1073
1542
|
}
|
|
1543
|
+
function mapComposerControlError(error) {
|
|
1544
|
+
if (error instanceof errors_1.WebWorkbenchError)
|
|
1545
|
+
return error;
|
|
1546
|
+
if (error instanceof agent_runtime_controller_1.FollowupQueueConflictError) {
|
|
1547
|
+
return new errors_1.WebWorkbenchError(409, error.message, error.code);
|
|
1548
|
+
}
|
|
1549
|
+
if (error instanceof session_composer_control_1.SessionComposerControlError) {
|
|
1550
|
+
const status = error.code === 'composer_recovery_required'
|
|
1551
|
+
? 503
|
|
1552
|
+
: ['runtime_busy', 'composer_control_conflict'].includes(error.code)
|
|
1553
|
+
? 409
|
|
1554
|
+
: 422;
|
|
1555
|
+
return new errors_1.WebWorkbenchError(status, error.message, error.code);
|
|
1556
|
+
}
|
|
1557
|
+
if (error instanceof plan_review_1.PlanReviewControlError) {
|
|
1558
|
+
const status = error.code === 'plan_review_invalid' ? 422 : 409;
|
|
1559
|
+
return new errors_1.WebWorkbenchError(status, error.message, error.code);
|
|
1560
|
+
}
|
|
1561
|
+
return new errors_1.WebWorkbenchError(503, 'The Session Composer control action failed.', 'composer_control_failed');
|
|
1562
|
+
}
|
|
1563
|
+
function mapContextReferenceError(error) {
|
|
1564
|
+
if (error instanceof errors_1.WebWorkbenchError) {
|
|
1565
|
+
if (error.code.startsWith('context_reference_'))
|
|
1566
|
+
return error;
|
|
1567
|
+
if (error.status === 403 ||
|
|
1568
|
+
['sensitive_file_blocked', 'file_binary', 'file_not_regular'].includes(error.code)) {
|
|
1569
|
+
return new errors_1.WebWorkbenchError(403, 'The Context reference is not available to the model.', 'context_reference_forbidden');
|
|
1570
|
+
}
|
|
1571
|
+
if (error.status === 404 || error.status === 409) {
|
|
1572
|
+
return new errors_1.WebWorkbenchError(409, 'The Context reference changed or is no longer available.', 'context_reference_stale');
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return new errors_1.WebWorkbenchError(422, 'The Context reference could not be resolved.', 'context_reference_invalid');
|
|
1576
|
+
}
|
|
1577
|
+
function composerAuthorityDigest(state) {
|
|
1578
|
+
return (0, canonical_1.digestRuntimeValue)({
|
|
1579
|
+
workspaceId: state.workspaceId,
|
|
1580
|
+
sessionId: state.sessionId,
|
|
1581
|
+
mode: state.mode,
|
|
1582
|
+
model: state.model,
|
|
1583
|
+
permission: state.permission,
|
|
1584
|
+
pending: state.pending,
|
|
1585
|
+
lastError: state.lastError,
|
|
1586
|
+
planReview: state.planReview,
|
|
1587
|
+
queue: state.queue,
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1074
1590
|
function pickPlanReceipt(receipt) {
|
|
1075
1591
|
return Object.freeze({
|
|
1076
1592
|
body: receipt.plan,
|