@noodleseed/one 0.140.0 → 0.141.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +0 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +9 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/app-package/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/app-package/dist/index.js +1 -0
- package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.d.ts +2 -1
- package/node_modules/@noodle-borg/app-package/package.json +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.d.ts +13 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.js +204 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +10 -7
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +39 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.js +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.d.ts +110 -0
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.js +217 -0
- package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +5 -0
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +1 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -2
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +19 -2
- package/node_modules/@noodle-borg/compiler/package.json +0 -2
- package/node_modules/@noodle-borg/compute/dist/code-connector.js +44 -1
- package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +82 -266
- package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +276 -0
- package/node_modules/@noodle-borg/compute/dist/worker-pool.js +262 -0
- package/node_modules/@noodle-borg/{service/dist/auth/deploy-gate.d.ts → control-plane/dist/deploy-gates.d.ts} +10 -6
- package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +266 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/capabilities.js +11 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/index.js +1 -1
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.d.ts +6 -0
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.js +14 -0
- package/node_modules/@noodle-borg/protocol/dist/execution-error-mapping.js +13 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +2 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.js +2 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.js +5 -0
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +15 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/types.js +6 -0
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/index.js +1 -1
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handles.d.ts +15 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handles.js +54 -44
- package/node_modules/@noodle-borg/service/dist/auth/developer-grant-guard.js +2 -1
- package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +15 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-clients.js +116 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +12 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +4 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +8 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +3 -9
- package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +11 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-sandbox.js +12 -47
- package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +34 -8
- package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +27 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +28 -130
- package/node_modules/@noodle-borg/service/dist/routes/config-values.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +8 -2
- package/node_modules/@noodle-borg/service/dist/service.js +15 -1
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +17 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +25 -0
- package/node_modules/@noodleseed/assistant/README.md +4 -9
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
- /package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.js +0 -0
|
@@ -12,6 +12,16 @@ const TOOL_EXECUTION_ERRORS = new Set([
|
|
|
12
12
|
]);
|
|
13
13
|
/** Execution-error codes surfaced as JSON-RPC `-32602` (a malformed/unknown request). */
|
|
14
14
|
const INVALID_PARAMS_ERRORS = new Set(['unknown_tool']);
|
|
15
|
+
/**
|
|
16
|
+
* The allowlisted `connector_error` reasons that reach the wire as structured diagnostics. These
|
|
17
|
+
* are stable classifications (oversized response; execution or queue-wait time-budget expiry) —
|
|
18
|
+
* anything else stays a bare `connector_error` so backend detail never leaks.
|
|
19
|
+
*/
|
|
20
|
+
const CONNECTOR_ERROR_STRUCTURED_REASONS = new Set([
|
|
21
|
+
'response_too_large',
|
|
22
|
+
'timeout',
|
|
23
|
+
'queue_timeout',
|
|
24
|
+
]);
|
|
15
25
|
/**
|
|
16
26
|
* Map an execution error to its MCP channel. Call-time, model-correctable failures become an
|
|
17
27
|
* `isError: true` tool result; an unknown tool is an invalid-params protocol error; everything else
|
|
@@ -54,7 +64,9 @@ export function mapExecutionError(error) {
|
|
|
54
64
|
result: {
|
|
55
65
|
content: [{ type: 'text', text }],
|
|
56
66
|
...(error.code === 'credential_unavailable' ||
|
|
57
|
-
(error.code === 'connector_error' &&
|
|
67
|
+
(error.code === 'connector_error' &&
|
|
68
|
+
error.reason !== undefined &&
|
|
69
|
+
CONNECTOR_ERROR_STRUCTURED_REASONS.has(error.reason))
|
|
58
70
|
? { structuredContent: { error: structuredError } }
|
|
59
71
|
: {}),
|
|
60
72
|
isError: true,
|
|
@@ -21,7 +21,8 @@ export interface ProtocolObservation {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* The analytics `errorKind` for an execution error: the stable code, refined with the allowlisted
|
|
24
|
-
* `reason` when one exists (`connector_error.timeout`). Analytics-only — wire
|
|
24
|
+
* `reason` when one exists (`connector_error.timeout`). Analytics-only — the wire's structured
|
|
25
|
+
* reasons are gated separately (and more narrowly) in `execution-error-mapping.ts`.
|
|
25
26
|
*/
|
|
26
27
|
export declare function executionErrorKind(error: {
|
|
27
28
|
readonly code: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The analytics `errorKind` for an execution error: the stable code, refined with the allowlisted
|
|
3
|
-
* `reason` when one exists (`connector_error.timeout`). Analytics-only — wire
|
|
3
|
+
* `reason` when one exists (`connector_error.timeout`). Analytics-only — the wire's structured
|
|
4
|
+
* reasons are gated separately (and more narrowly) in `execution-error-mapping.ts`.
|
|
4
5
|
*/
|
|
5
6
|
export function executionErrorKind(error) {
|
|
6
7
|
return error.reason === undefined ? error.code : `${error.code}.${error.reason}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConnectorTraceEvent } from './types.js';
|
|
2
|
-
type ConnectorFailureDetails = Pick<ConnectorTraceEvent, 'status' | 'category' | 'attempts' | 'retryable' | 'retryAfterMs' | 'responseExcerpt'>;
|
|
2
|
+
type ConnectorFailureDetails = Pick<ConnectorTraceEvent, 'status' | 'category' | 'attempts' | 'retryable' | 'retryAfterMs' | 'responseExcerpt' | 'queueWaitMs' | 'executionMs'>;
|
|
3
3
|
/** Reconstruct connector failure metadata from runtime-validated own data properties only. */
|
|
4
4
|
export declare function sanitizeConnectorFailureDetails(error: unknown, options?: {
|
|
5
5
|
readonly includeResponseExcerpt?: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const FAILURE_CATEGORIES = new Set([
|
|
2
2
|
'timeout',
|
|
3
|
+
'queue_timeout',
|
|
3
4
|
'network_error',
|
|
4
5
|
'rate_limited',
|
|
5
6
|
'upstream_5xx',
|
|
@@ -20,6 +21,8 @@ export function sanitizeConnectorFailureDetails(error, options = {}) {
|
|
|
20
21
|
const responseExcerpt = options.includeResponseExcerpt
|
|
21
22
|
? ownData(error, 'responseExcerpt')
|
|
22
23
|
: undefined;
|
|
24
|
+
const queueWaitMs = ownData(error, 'queueWaitMs');
|
|
25
|
+
const executionMs = ownData(error, 'executionMs');
|
|
23
26
|
return Object.freeze({
|
|
24
27
|
...(isIntegerInRange(status, 100, 599) ? { status } : {}),
|
|
25
28
|
...(typeof category === 'string' && FAILURE_CATEGORIES.has(category)
|
|
@@ -31,6 +34,8 @@ export function sanitizeConnectorFailureDetails(error, options = {}) {
|
|
|
31
34
|
...(typeof responseExcerpt === 'string' && responseExcerpt.length <= MAX_RESPONSE_EXCERPT_LENGTH
|
|
32
35
|
? { responseExcerpt }
|
|
33
36
|
: {}),
|
|
37
|
+
...(isIntegerInRange(queueWaitMs, 0, MAX_RETRY_AFTER_MS) ? { queueWaitMs } : {}),
|
|
38
|
+
...(isIntegerInRange(executionMs, 0, MAX_RETRY_AFTER_MS) ? { executionMs } : {}),
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
function ownData(error, key) {
|
|
@@ -34,11 +34,15 @@ export interface ConnectorTraceEvent {
|
|
|
34
34
|
readonly retryable?: boolean;
|
|
35
35
|
readonly retryAfterMs?: number;
|
|
36
36
|
readonly responseExcerpt?: string;
|
|
37
|
+
/** Time the call waited for an execution slot, timed independently of execution. */
|
|
38
|
+
readonly queueWaitMs?: number;
|
|
39
|
+
/** Time the call actually executed, excluding queue wait. */
|
|
40
|
+
readonly executionMs?: number;
|
|
37
41
|
}
|
|
38
42
|
export interface ExecutionTraceSink {
|
|
39
43
|
record(event: ConnectorTraceEvent): void;
|
|
40
44
|
}
|
|
41
|
-
export type ConnectorFailureCategory = 'timeout' | 'network_error' | 'rate_limited' | 'upstream_5xx' | 'upstream_4xx' | 'invalid_response' | 'response_too_large';
|
|
45
|
+
export type ConnectorFailureCategory = 'timeout' | 'queue_timeout' | 'network_error' | 'rate_limited' | 'upstream_5xx' | 'upstream_4xx' | 'invalid_response' | 'response_too_large';
|
|
42
46
|
export declare class ConnectorInvocationError extends Error {
|
|
43
47
|
readonly status?: number;
|
|
44
48
|
readonly category?: ConnectorFailureCategory;
|
|
@@ -46,6 +50,8 @@ export declare class ConnectorInvocationError extends Error {
|
|
|
46
50
|
readonly retryable?: boolean;
|
|
47
51
|
readonly retryAfterMs?: number;
|
|
48
52
|
readonly responseExcerpt?: string;
|
|
53
|
+
readonly queueWaitMs?: number;
|
|
54
|
+
readonly executionMs?: number;
|
|
49
55
|
constructor(message: string, details?: {
|
|
50
56
|
readonly status?: number;
|
|
51
57
|
readonly category?: ConnectorFailureCategory;
|
|
@@ -53,6 +59,8 @@ export declare class ConnectorInvocationError extends Error {
|
|
|
53
59
|
readonly retryable?: boolean;
|
|
54
60
|
readonly retryAfterMs?: number;
|
|
55
61
|
readonly responseExcerpt?: string;
|
|
62
|
+
readonly queueWaitMs?: number;
|
|
63
|
+
readonly executionMs?: number;
|
|
56
64
|
});
|
|
57
65
|
}
|
|
58
66
|
/** Trap-free nominal classification for connector rejection values. Proxy wrappers are untrusted. */
|
|
@@ -89,6 +97,12 @@ export interface ConnectorCall {
|
|
|
89
97
|
readonly route?: CustomerConnectorRoute;
|
|
90
98
|
/** Optional host capability for sandboxed connectors; ignored by ordinary connectors. */
|
|
91
99
|
readonly host?: ConnectorCallHost;
|
|
100
|
+
/**
|
|
101
|
+
* Optional execution trace sink for connector-side observability (e.g. sandbox queue-wait vs
|
|
102
|
+
* execution durations). Connectors record only flat scalar fields; the runtime still owns the
|
|
103
|
+
* failure-event path.
|
|
104
|
+
*/
|
|
105
|
+
readonly trace?: ExecutionTraceSink;
|
|
92
106
|
}
|
|
93
107
|
/**
|
|
94
108
|
* A connector: a versioned integration exposing typed operations. Phase 1 ships only an in-memory
|
|
@@ -7,6 +7,8 @@ export class ConnectorInvocationError extends Error {
|
|
|
7
7
|
retryable;
|
|
8
8
|
retryAfterMs;
|
|
9
9
|
responseExcerpt;
|
|
10
|
+
queueWaitMs;
|
|
11
|
+
executionMs;
|
|
10
12
|
constructor(message, details = {}) {
|
|
11
13
|
super(message);
|
|
12
14
|
connectorInvocationErrors.add(this);
|
|
@@ -24,6 +26,10 @@ export class ConnectorInvocationError extends Error {
|
|
|
24
26
|
this.retryAfterMs = details.retryAfterMs;
|
|
25
27
|
if (details.responseExcerpt !== undefined)
|
|
26
28
|
this.responseExcerpt = details.responseExcerpt;
|
|
29
|
+
if (details.queueWaitMs !== undefined)
|
|
30
|
+
this.queueWaitMs = details.queueWaitMs;
|
|
31
|
+
if (details.executionMs !== undefined)
|
|
32
|
+
this.executionMs = details.executionMs;
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
35
|
/** Trap-free nominal classification for connector rejection values. Proxy wrappers are untrusted. */
|
|
@@ -18,6 +18,6 @@ export { AllowAllPolicy } from './policy/allow-all.js';
|
|
|
18
18
|
export type { PolicyContext, PolicyDecision, PolicyGate } from './policy/types.js';
|
|
19
19
|
export { type ConfirmationActionReview, type ConfirmationPreparationResult, type ConfirmationReview, type ConnectorFailureAttribution, type ElicitationRequest, type ElicitationResponse, type ExecutionError, type ExecutionErrorCode, type ExecutionResult, type InteractiveExecutionResult, isConfirmationRequired, isInputRequired, isInputRequiredForConfirmation, type PreparedOperationAction, type PreparedToolContinuation, type ToolContinuation, type ToolPreparationContinuation, } from './result.js';
|
|
20
20
|
export { splitResultMeta } from './result-meta.js';
|
|
21
|
-
export { assertNoSecretValue, assertSchemaValue, COMPLETE_STATE_OPERATION, createStateConnector, InMemoryStateHandleStore, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, type StateHandleRecord, type StateHandleStore, type StateInput, type StateMutationInput, type StatePatchInput, } from './state-handles.js';
|
|
21
|
+
export { assertExpectedStateRevision, assertNoSecretValue, assertSchemaValue, assertStateHandleNotCompleted, COMPLETE_STATE_OPERATION, createMutableStateHandleRecord, createStateConnector, InMemoryStateHandleStore, type MutableStateHandleRecord, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, type StateHandleRecord, type StateHandleStore, type StateInput, type StateMutationInput, type StatePatchInput, stateHandleRecordForMutation, toPublicStateHandleRecord, } from './state-handles.js';
|
|
22
22
|
export type { ToolDispatchContext, ToolDispatchDecision, ToolDispatchHook, } from './tool-dispatch.js';
|
|
23
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -15,5 +15,5 @@ export { resolveManagedOrigins, } from './managed-origins.js';
|
|
|
15
15
|
export { AllowAllPolicy } from './policy/allow-all.js';
|
|
16
16
|
export { isConfirmationRequired, isInputRequired, isInputRequiredForConfirmation, } from './result.js';
|
|
17
17
|
export { splitResultMeta } from './result-meta.js';
|
|
18
|
-
export { assertNoSecretValue, assertSchemaValue, COMPLETE_STATE_OPERATION, createStateConnector, InMemoryStateHandleStore, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, } from './state-handles.js';
|
|
18
|
+
export { assertExpectedStateRevision, assertNoSecretValue, assertSchemaValue, assertStateHandleNotCompleted, COMPLETE_STATE_OPERATION, createMutableStateHandleRecord, createStateConnector, InMemoryStateHandleStore, PATCH_STATE_OPERATION, READ_STATE_OPERATION, STATE_CONNECTOR_ID, STATE_CONNECTOR_VERSION, STATE_OPERATION_SIGNATURES, StateConnector, stateHandleRecordForMutation, toPublicStateHandleRecord, } from './state-handles.js';
|
|
19
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -171,6 +171,7 @@ async function invokeOperation(ref, args, sig, toolName, deps, policy, host, env
|
|
|
171
171
|
...(deps.caller !== undefined ? { caller: deps.caller } : {}),
|
|
172
172
|
...(customerRoute === undefined ? {} : { route: customerRoute }),
|
|
173
173
|
host,
|
|
174
|
+
...(deps.trace === undefined ? {} : { trace: deps.trace }),
|
|
174
175
|
});
|
|
175
176
|
}
|
|
176
177
|
catch (error) {
|
|
@@ -14,6 +14,16 @@ export interface StateHandleRecord {
|
|
|
14
14
|
readonly status: 'active' | 'completed' | 'expired';
|
|
15
15
|
readonly expiresAt?: string;
|
|
16
16
|
}
|
|
17
|
+
export interface MutableStateHandleRecord {
|
|
18
|
+
handle: string;
|
|
19
|
+
key: string;
|
|
20
|
+
value: Record<string, unknown>;
|
|
21
|
+
revision: number;
|
|
22
|
+
status: 'active' | 'completed';
|
|
23
|
+
createdAt: number;
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
expiresAt?: number;
|
|
26
|
+
}
|
|
17
27
|
export interface StateHandleStore {
|
|
18
28
|
read(input: StateInput): Promise<StateHandleRecord> | StateHandleRecord;
|
|
19
29
|
patch(input: StatePatchInput): Promise<StateHandleRecord> | StateHandleRecord;
|
|
@@ -46,6 +56,11 @@ export declare class StateConnector implements Connector {
|
|
|
46
56
|
invoke(call: ConnectorCall): Promise<unknown>;
|
|
47
57
|
}
|
|
48
58
|
export declare function createStateConnector(state: ArtifactState | undefined): StateConnector | undefined;
|
|
59
|
+
export declare function createMutableStateHandleRecord(handle: string, key: string, ttlSeconds: number | undefined, now: number): MutableStateHandleRecord;
|
|
60
|
+
export declare function stateHandleRecordForMutation(record: MutableStateHandleRecord | undefined, handle: string, key: string, ttlSeconds: number | undefined, now: number): MutableStateHandleRecord;
|
|
61
|
+
export declare function assertStateHandleNotCompleted(record: MutableStateHandleRecord): void;
|
|
62
|
+
export declare function assertExpectedStateRevision(record: MutableStateHandleRecord, expectedRevision: number): void;
|
|
63
|
+
export declare function toPublicStateHandleRecord(record: MutableStateHandleRecord, now: number): StateHandleRecord;
|
|
49
64
|
export declare function assertNoSecretValue(value: unknown, path?: string): void;
|
|
50
65
|
export declare function assertSchemaValue(schema: Record<string, unknown>, value: Record<string, unknown>): void;
|
|
51
66
|
//# sourceMappingURL=state-handles.d.ts.map
|
|
@@ -55,18 +55,20 @@ export class InMemoryStateHandleStore {
|
|
|
55
55
|
read(input) {
|
|
56
56
|
const def = this.#definition(input.handle);
|
|
57
57
|
const key = this.#recordKey(def, input);
|
|
58
|
-
|
|
58
|
+
const now = this.#now().getTime();
|
|
59
|
+
return toPublicStateHandleRecord(this.#records.get(key) ??
|
|
60
|
+
createMutableStateHandleRecord(input.handle, input.key ?? 'default', def.ttlSeconds, now), now);
|
|
59
61
|
}
|
|
60
62
|
patch(input) {
|
|
61
63
|
const def = this.#definition(input.handle);
|
|
62
64
|
const key = this.#recordKey(def, input);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const now = this.#now().getTime();
|
|
66
|
+
const current = stateHandleRecordForMutation(this.#records.get(key), input.handle, input.key ?? 'default', def.ttlSeconds, now);
|
|
67
|
+
assertStateHandleNotCompleted(current);
|
|
68
|
+
assertExpectedStateRevision(current, input.expectedRevision);
|
|
66
69
|
assertNoSecretValue(input.value);
|
|
67
70
|
const value = { ...current.value, ...input.value };
|
|
68
71
|
assertSchemaValue(def.schema, value);
|
|
69
|
-
const now = this.#now().getTime();
|
|
70
72
|
const next = {
|
|
71
73
|
...current,
|
|
72
74
|
value,
|
|
@@ -74,22 +76,23 @@ export class InMemoryStateHandleStore {
|
|
|
74
76
|
updatedAt: now,
|
|
75
77
|
};
|
|
76
78
|
this.#records.set(key, next);
|
|
77
|
-
return
|
|
79
|
+
return toPublicStateHandleRecord(next, now);
|
|
78
80
|
}
|
|
79
81
|
complete(input) {
|
|
80
82
|
const def = this.#definition(input.handle);
|
|
81
83
|
const key = this.#recordKey(def, input);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
const now = this.#now().getTime();
|
|
85
|
+
const current = stateHandleRecordForMutation(this.#records.get(key), input.handle, input.key ?? 'default', def.ttlSeconds, now);
|
|
86
|
+
assertStateHandleNotCompleted(current);
|
|
87
|
+
assertExpectedStateRevision(current, input.expectedRevision);
|
|
85
88
|
const next = {
|
|
86
89
|
...current,
|
|
87
90
|
status: 'completed',
|
|
88
91
|
revision: current.revision + 1,
|
|
89
|
-
updatedAt:
|
|
92
|
+
updatedAt: now,
|
|
90
93
|
};
|
|
91
94
|
this.#records.set(key, next);
|
|
92
|
-
return
|
|
95
|
+
return toPublicStateHandleRecord(next, now);
|
|
93
96
|
}
|
|
94
97
|
#definition(handle) {
|
|
95
98
|
const def = this.#state.handles[handle];
|
|
@@ -102,34 +105,6 @@ export class InMemoryStateHandleStore {
|
|
|
102
105
|
const owner = def.scope === 'caller' ? (input.callerSubject ?? 'anonymous') : 'deployment';
|
|
103
106
|
return `${input.handle}\u0000${owner}\u0000${userKey}`;
|
|
104
107
|
}
|
|
105
|
-
#emptyRecord(handle, key, def) {
|
|
106
|
-
const now = this.#now().getTime();
|
|
107
|
-
return {
|
|
108
|
-
handle,
|
|
109
|
-
key,
|
|
110
|
-
value: {},
|
|
111
|
-
revision: 0,
|
|
112
|
-
status: 'active',
|
|
113
|
-
createdAt: now,
|
|
114
|
-
updatedAt: now,
|
|
115
|
-
...(def.ttlSeconds !== undefined ? { expiresAt: now + def.ttlSeconds * 1000 } : {}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
#publicRecord(record) {
|
|
119
|
-
const status = record.expiresAt !== undefined && record.expiresAt <= this.#now().getTime()
|
|
120
|
-
? 'expired'
|
|
121
|
-
: record.status;
|
|
122
|
-
return {
|
|
123
|
-
handle: record.handle,
|
|
124
|
-
key: record.key,
|
|
125
|
-
value: record.value,
|
|
126
|
-
revision: record.revision,
|
|
127
|
-
status,
|
|
128
|
-
...(record.expiresAt !== undefined
|
|
129
|
-
? { expiresAt: new Date(record.expiresAt).toISOString() }
|
|
130
|
-
: {}),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
108
|
}
|
|
134
109
|
export class StateConnector {
|
|
135
110
|
id = STATE_CONNECTOR_ID;
|
|
@@ -212,18 +187,53 @@ function objectArg(value, name) {
|
|
|
212
187
|
throw new Error(`${name} must be an object`);
|
|
213
188
|
return value;
|
|
214
189
|
}
|
|
215
|
-
function
|
|
190
|
+
export function createMutableStateHandleRecord(handle, key, ttlSeconds, now) {
|
|
191
|
+
return {
|
|
192
|
+
handle,
|
|
193
|
+
key,
|
|
194
|
+
value: {},
|
|
195
|
+
revision: 0,
|
|
196
|
+
status: 'active',
|
|
197
|
+
createdAt: now,
|
|
198
|
+
updatedAt: now,
|
|
199
|
+
...(ttlSeconds !== undefined ? { expiresAt: now + ttlSeconds * 1000 } : {}),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export function stateHandleRecordForMutation(record, handle, key, ttlSeconds, now) {
|
|
203
|
+
if (record === undefined)
|
|
204
|
+
return createMutableStateHandleRecord(handle, key, ttlSeconds, now);
|
|
205
|
+
if (!isExpired(record, now))
|
|
206
|
+
return record;
|
|
207
|
+
return {
|
|
208
|
+
...createMutableStateHandleRecord(handle, key, ttlSeconds, now),
|
|
209
|
+
revision: record.revision + 1,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function assertStateHandleNotCompleted(record) {
|
|
216
213
|
if (record.status === 'completed')
|
|
217
214
|
throw new Error('completed state handles are read-only');
|
|
218
|
-
if (record.expiresAt !== undefined && record.expiresAt <= now) {
|
|
219
|
-
throw new Error('expired state handles are read-only');
|
|
220
|
-
}
|
|
221
215
|
}
|
|
222
|
-
function
|
|
216
|
+
function isExpired(record, now) {
|
|
217
|
+
return record.expiresAt !== undefined && record.expiresAt <= now;
|
|
218
|
+
}
|
|
219
|
+
export function assertExpectedStateRevision(record, expectedRevision) {
|
|
223
220
|
if (record.revision !== expectedRevision) {
|
|
224
221
|
throw new Error(`state revision conflict: expected ${expectedRevision}, got ${record.revision}`);
|
|
225
222
|
}
|
|
226
223
|
}
|
|
224
|
+
export function toPublicStateHandleRecord(record, now) {
|
|
225
|
+
const expired = isExpired(record, now);
|
|
226
|
+
return {
|
|
227
|
+
handle: record.handle,
|
|
228
|
+
key: record.key,
|
|
229
|
+
value: record.value,
|
|
230
|
+
revision: record.revision + (expired ? 1 : 0),
|
|
231
|
+
status: expired ? 'expired' : record.status,
|
|
232
|
+
...(record.expiresAt !== undefined
|
|
233
|
+
? { expiresAt: new Date(record.expiresAt).toISOString() }
|
|
234
|
+
: {}),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
227
237
|
const SECRET_LIKE_FIELD = /(secret|token|api[_-]?key|password|credential|authorization|cookie)/i;
|
|
228
238
|
export function assertNoSecretValue(value, path = 'value') {
|
|
229
239
|
if (Array.isArray(value)) {
|
|
@@ -8,7 +8,8 @@ export async function authorizeDeveloperGrant(identity, context) {
|
|
|
8
8
|
});
|
|
9
9
|
if (grant === undefined)
|
|
10
10
|
return false;
|
|
11
|
-
|
|
11
|
+
const grantPath = developerResourcePath(grant.resource);
|
|
12
|
+
if (grantPath === undefined || !context.resourcePaths.has(grantPath))
|
|
12
13
|
return false;
|
|
13
14
|
if (!grant.capabilities.includes(context.capability))
|
|
14
15
|
return false;
|
|
@@ -42,7 +42,7 @@ export function createModuleRouteContext(hostRequest, deps) {
|
|
|
42
42
|
controlPlane: deps.controlPlane,
|
|
43
43
|
org: input.org,
|
|
44
44
|
capability: input.permission,
|
|
45
|
-
|
|
45
|
+
resourcePaths: new Set([DEVELOPER_CLI_PATH]),
|
|
46
46
|
}));
|
|
47
47
|
if (!allowed) {
|
|
48
48
|
return forbidden('developer grant does not authorize this operation');
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { CapabilityName } from '@noodle-borg/capabilities';
|
|
2
2
|
import type { CatalogConnector, HostedPackagedAsset } from '@noodle-borg/compiler';
|
|
3
|
+
import type { ControlPlaneIdentity } from '@noodle-borg/control-plane/portable';
|
|
3
4
|
import type { OrgMembershipSource, PolicyGate } from '@noodle-borg/module';
|
|
4
5
|
import type { Connector } from '@noodle-borg/runtime';
|
|
5
6
|
import type { AccessMode, OwnerTokenVerifier } from '@noodle-borg/transport-http';
|
|
6
7
|
import type { DeploymentSource } from '@noodle-borg/wire-contracts';
|
|
7
8
|
import type { AppPackageRenderer } from './app-package-snapshot.js';
|
|
8
|
-
import type { ControlPlaneIdentity } from './auth/deploy-gate.js';
|
|
9
9
|
import type { DelegatedExchangeOptions } from './delegated-token-exchange.js';
|
|
10
10
|
import type { ExternalCredentialExchangeRuntimeOptions } from './external-credential-exchange.js';
|
|
11
11
|
import type { GoogleWorkloadIdentityRuntimeOptions } from './google-workload-identity.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
import { requestModelCompletion, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
3
|
-
import { assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, authenticatedSurfaceOf, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
5
5
|
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
6
6
|
import { evaluateToolAuthorization } from '@noodle-borg/protocol';
|
|
@@ -383,10 +383,22 @@ function selectTurnModelTools(target, caller, latestMessage, usedToolNames) {
|
|
|
383
383
|
...(usedToolNames === undefined ? {} : { usedToolNames }),
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
|
-
/**
|
|
386
|
+
/**
|
|
387
|
+
* Surface behavior is injected only for the exact surface the session is bound to (ADR 0215): the
|
|
388
|
+
* public-audience surface for embed-minted sessions, the authenticated surface for backend-exchanged
|
|
389
|
+
* ones. A session with no binding — a record from before binding existed, or a pre-surfaces artifact —
|
|
390
|
+
* fails closed to no surface block, never to a union or first-surface fallback.
|
|
391
|
+
*/
|
|
387
392
|
function surfaceInstructionsContext(assistant, session) {
|
|
388
|
-
|
|
393
|
+
const bound = session.boundSurface ?? (session.publicEmbedId !== undefined ? 'public' : undefined);
|
|
394
|
+
if (bound === undefined)
|
|
389
395
|
return '';
|
|
396
|
+
if (bound === 'authenticated') {
|
|
397
|
+
const surface = authenticatedSurfaceOf(assistant);
|
|
398
|
+
if (!surface?.instructions)
|
|
399
|
+
return '';
|
|
400
|
+
return `\n\nSurface instructions (authenticated website surface; same trust level as tenant instructions):\n${surface.instructions}`;
|
|
401
|
+
}
|
|
390
402
|
const surface = publicSurfaceOf(assistant);
|
|
391
403
|
if (!surface?.instructions)
|
|
392
404
|
return '';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readJsonBody, sendForbidden, sendJson } from '../http-util.js';
|
|
2
|
+
import { now } from './assistant-route-http.js';
|
|
3
|
+
import { authorizeControlPlane } from './control-plane.js';
|
|
4
|
+
export async function handleAssistantClients(req, res, tenant, id, action, deps) {
|
|
5
|
+
const identity = await authorizeControlPlane(req, res, deps.gate, { requireIdentity: false });
|
|
6
|
+
if (identity === false)
|
|
7
|
+
return;
|
|
8
|
+
if (identity && !identity.superAdmin) {
|
|
9
|
+
const member = await deps.controlPlane.isOrgMember({
|
|
10
|
+
org: tenant.org,
|
|
11
|
+
subject: identity.subject,
|
|
12
|
+
});
|
|
13
|
+
if (!member)
|
|
14
|
+
return sendForbidden(res, 'forbidden');
|
|
15
|
+
}
|
|
16
|
+
if (action === 'collection' && req.method === 'GET') {
|
|
17
|
+
const clients = await deps.store.listClients(tenant);
|
|
18
|
+
return sendJson(res, 200, {
|
|
19
|
+
ok: true,
|
|
20
|
+
clients: clients.map(publicClient),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (action === 'collection' && req.method === 'POST') {
|
|
24
|
+
const body = await readJsonBody(req, deps.maxBody);
|
|
25
|
+
if (!body.ok)
|
|
26
|
+
return sendJson(res, body.status, { error: body.error });
|
|
27
|
+
const name = body.value.name;
|
|
28
|
+
if (typeof name !== 'string' || name.trim().length < 1 || name.length > 80) {
|
|
29
|
+
return sendJson(res, 400, { error: '"name" must be a non-empty string' });
|
|
30
|
+
}
|
|
31
|
+
const target = await deps.registry.getActiveByTenant(tenant);
|
|
32
|
+
const assistant = target?.served.artifact.server.assistant;
|
|
33
|
+
if (!target?.deploymentId || !assistant)
|
|
34
|
+
return sendJson(res, 409, { error: 'deployment has no embedded assistant' });
|
|
35
|
+
const created = await deps.store.createClient({
|
|
36
|
+
name: name.trim(),
|
|
37
|
+
tenant,
|
|
38
|
+
deploymentId: target.deploymentId,
|
|
39
|
+
allowedOrigins: assistant.allowedOrigins,
|
|
40
|
+
now: now(deps),
|
|
41
|
+
});
|
|
42
|
+
await deps.audit.emit({
|
|
43
|
+
eventType: 'assistant.client.created',
|
|
44
|
+
org: tenant.org,
|
|
45
|
+
app: tenant.app,
|
|
46
|
+
env: tenant.env,
|
|
47
|
+
deploymentId: target.deploymentId,
|
|
48
|
+
decision: 'allow',
|
|
49
|
+
status: 201,
|
|
50
|
+
...(identity ? { actorSubject: identity.subject } : {}),
|
|
51
|
+
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
52
|
+
details: { clientId: created.client.id, name: created.client.name },
|
|
53
|
+
});
|
|
54
|
+
return sendJson(res, 201, {
|
|
55
|
+
ok: true,
|
|
56
|
+
...publicClient(created.client),
|
|
57
|
+
clientSecret: created.secret,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (!id)
|
|
61
|
+
return sendJson(res, 404, { error: 'not found' });
|
|
62
|
+
const owned = (await deps.store.listClients(tenant)).some((client) => client.id === id);
|
|
63
|
+
if (!owned)
|
|
64
|
+
return sendJson(res, 404, { error: 'not found' });
|
|
65
|
+
if (action === 'rotate' && req.method === 'POST') {
|
|
66
|
+
const rotated = await deps.store.rotateClient(id, now(deps));
|
|
67
|
+
if (!rotated)
|
|
68
|
+
return sendJson(res, 404, { error: 'not found' });
|
|
69
|
+
await deps.audit.emit({
|
|
70
|
+
eventType: 'assistant.client.rotated',
|
|
71
|
+
org: tenant.org,
|
|
72
|
+
app: tenant.app,
|
|
73
|
+
env: tenant.env,
|
|
74
|
+
deploymentId: rotated.client.deploymentId,
|
|
75
|
+
decision: 'allow',
|
|
76
|
+
status: 200,
|
|
77
|
+
...(identity ? { actorSubject: identity.subject } : {}),
|
|
78
|
+
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
79
|
+
details: { clientId: rotated.client.id },
|
|
80
|
+
});
|
|
81
|
+
return sendJson(res, 200, {
|
|
82
|
+
ok: true,
|
|
83
|
+
...publicClient(rotated.client),
|
|
84
|
+
clientSecret: rotated.secret,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (action === 'item' && req.method === 'DELETE') {
|
|
88
|
+
await deps.store.revokeClient(id, now(deps));
|
|
89
|
+
await deps.audit.emit({
|
|
90
|
+
eventType: 'assistant.client.revoked',
|
|
91
|
+
org: tenant.org,
|
|
92
|
+
app: tenant.app,
|
|
93
|
+
env: tenant.env,
|
|
94
|
+
decision: 'allow',
|
|
95
|
+
status: 204,
|
|
96
|
+
...(identity ? { actorSubject: identity.subject } : {}),
|
|
97
|
+
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
98
|
+
details: { clientId: id },
|
|
99
|
+
});
|
|
100
|
+
res.statusCode = 204;
|
|
101
|
+
res.end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
sendJson(res, 405, { error: 'method not allowed' });
|
|
105
|
+
}
|
|
106
|
+
function publicClient(client) {
|
|
107
|
+
return {
|
|
108
|
+
id: client.id,
|
|
109
|
+
name: client.name,
|
|
110
|
+
createdAt: client.createdAt,
|
|
111
|
+
// Audit data only: sessions follow the tenant's active deployment, not this snapshot.
|
|
112
|
+
createdAgainstDeploymentId: client.deploymentId,
|
|
113
|
+
...(client.revokedAt ? { revokedAt: client.revokedAt } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=assistant-clients.js.map
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAssistantSandboxHost, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { handleAssistantAppRequest, handleAssistantPreflight, handleAssistantSession, handleAssistantTurn, handleConsoleApprovalNonce, } from './assistant.js';
|
|
3
|
+
import { handleAssistantClients } from './assistant-clients.js';
|
|
2
4
|
import { handleAssistantDoctor } from './assistant-doctor.js';
|
|
3
5
|
import { handleAssistantEmbedScript } from './assistant-embed-script.js';
|
|
4
6
|
import { handleAssistantEmbeds } from './assistant-embeds.js';
|
|
5
7
|
import { handleAssistantConfirmation, handleAssistantInteraction, } from './assistant-interactions.js';
|
|
6
8
|
import { handlePublicAssistantSession } from './assistant-public-session.js';
|
|
7
9
|
import { handleAssistantSandbox } from './assistant-sandbox.js';
|
|
10
|
+
import { handleAssistantTranscript } from './assistant-transcript.js';
|
|
8
11
|
export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
9
12
|
if (url.pathname === '/v1/console/approval-nonces/consume' && req.method === 'POST') {
|
|
10
13
|
return run(req, res, deps, () => handleConsoleApprovalNonce(req, res, deps));
|
|
@@ -13,7 +16,8 @@ export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
|
13
16
|
url.pathname === '/v1/assistant/turns' ||
|
|
14
17
|
url.pathname === '/v1/assistant/tool-confirmations' ||
|
|
15
18
|
url.pathname === '/v1/assistant/interactions' ||
|
|
16
|
-
url.pathname === '/v1/assistant/apps'
|
|
19
|
+
url.pathname === '/v1/assistant/apps' ||
|
|
20
|
+
url.pathname === '/v1/assistant/transcript') &&
|
|
17
21
|
req.method === 'OPTIONS') {
|
|
18
22
|
deps.applySecurityHeaders(res, deps.tls);
|
|
19
23
|
handleAssistantPreflight(req, res);
|
|
@@ -32,7 +36,9 @@ export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
|
32
36
|
deps.applySecurityHeaders(res, deps.tls);
|
|
33
37
|
if (deps.enforceHttps(req, res, deps.tls))
|
|
34
38
|
return true;
|
|
35
|
-
|
|
39
|
+
const sandboxBase = deps.sandboxBase?.(req);
|
|
40
|
+
const isolatedOrigin = isAssistantSandboxHost(req.headers.host, sandboxBase);
|
|
41
|
+
handleAssistantSandbox(req, res, { isolatedOrigin });
|
|
36
42
|
return true;
|
|
37
43
|
}
|
|
38
44
|
if (url.pathname === '/v1/assistant/public-sessions' && req.method === 'POST') {
|
|
@@ -47,6 +53,9 @@ export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
|
47
53
|
if (url.pathname === '/v1/assistant/apps' && req.method === 'POST') {
|
|
48
54
|
return run(req, res, deps, () => handleAssistantAppRequest(req, res, deps));
|
|
49
55
|
}
|
|
56
|
+
if (url.pathname === '/v1/assistant/transcript' && req.method === 'POST') {
|
|
57
|
+
return run(req, res, deps, () => handleAssistantTranscript(req, res, deps));
|
|
58
|
+
}
|
|
50
59
|
const doctorMatch = /^\/v1\/orgs\/([^/]+)\/apps\/([^/]+)\/envs\/([^/]+)\/assistant\/doctor$/.exec(url.pathname);
|
|
51
60
|
if (doctorMatch) {
|
|
52
61
|
const tenant = decodeTenant(doctorMatch);
|
|
@@ -93,6 +93,7 @@ export async function elevateAssistantSession(res, deps, input) {
|
|
|
93
93
|
clientId: input.client.id,
|
|
94
94
|
origin: input.origin,
|
|
95
95
|
...(input.customerRouting ? { customerRouting: input.customerRouting } : {}),
|
|
96
|
+
...(input.boundSurface ? { boundSurface: input.boundSurface } : {}),
|
|
96
97
|
resume: input.resume,
|
|
97
98
|
}, {
|
|
98
99
|
elevations: deps.elevations,
|
|
@@ -5,7 +5,10 @@ export async function narrateResolvedInteraction(res, deps, target, session, too
|
|
|
5
5
|
try {
|
|
6
6
|
const narration = await narrateInteractionResolution(target, session, tool, action, result, context, deps, (delta) => res.write(`event: content\ndata: ${JSON.stringify({ delta })}\n\n`));
|
|
7
7
|
if (narration) {
|
|
8
|
-
|
|
8
|
+
// Streamed to the panel as content deltas above, so it is genuine visible prose.
|
|
9
|
+
await deps.store.appendHistory(session.id, [
|
|
10
|
+
{ role: 'assistant', content: narration, kind: 'visible' },
|
|
11
|
+
]);
|
|
9
12
|
}
|
|
10
13
|
}
|
|
11
14
|
catch {
|
|
@@ -341,7 +341,12 @@ async function finishExecution(res, deps, session, interaction, context, result,
|
|
|
341
341
|
status: 'succeeded',
|
|
342
342
|
});
|
|
343
343
|
await deps.store.appendHistory(session.id, [
|
|
344
|
-
|
|
344
|
+
// Model-facing scaffolding: the panel saw the tool_completed card, never this row's tool JSON.
|
|
345
|
+
{
|
|
346
|
+
role: 'assistant',
|
|
347
|
+
content: `Completed ${toolName}: ${JSON.stringify(safeOutput)}`,
|
|
348
|
+
kind: 'narration',
|
|
349
|
+
},
|
|
345
350
|
]);
|
|
346
351
|
writeInteractionSseHeaders(res, session.origin);
|
|
347
352
|
writeInteractionResolved(res, interaction.id, 'accept');
|
|
@@ -412,9 +417,11 @@ async function resolveStop(res, deps, session, interaction, action) {
|
|
|
412
417
|
});
|
|
413
418
|
const toolName = assistantInteractionTool(completion.interaction) ?? 'tool request';
|
|
414
419
|
await deps.store.appendHistory(session.id, [
|
|
420
|
+
// Model-facing scaffolding: the panel saw the interaction resolve, never this summary row.
|
|
415
421
|
{
|
|
416
422
|
role: 'assistant',
|
|
417
423
|
content: `${action === 'decline' ? 'Declined' : 'Cancelled'} ${toolName}; it was not executed.`,
|
|
424
|
+
kind: 'narration',
|
|
418
425
|
},
|
|
419
426
|
]);
|
|
420
427
|
writeInteractionSseHeaders(res, session.origin);
|