@gajae-code/coding-agent 0.3.2 → 0.4.0

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.
Files changed (122) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/types/config/model-registry.d.ts +17 -10
  3. package/dist/types/config/models-config-schema.d.ts +37 -0
  4. package/dist/types/config/settings-schema.d.ts +5 -0
  5. package/dist/types/edit/diff.d.ts +16 -0
  6. package/dist/types/edit/modes/replace.d.ts +7 -0
  7. package/dist/types/extensibility/gjc-plugins/activation.d.ts +14 -0
  8. package/dist/types/extensibility/gjc-plugins/index.d.ts +9 -0
  9. package/dist/types/extensibility/gjc-plugins/injection.d.ts +31 -0
  10. package/dist/types/extensibility/gjc-plugins/loader.d.ts +3 -0
  11. package/dist/types/extensibility/gjc-plugins/paths.d.ts +8 -0
  12. package/dist/types/extensibility/gjc-plugins/schema.d.ts +3 -0
  13. package/dist/types/extensibility/gjc-plugins/state.d.ts +9 -0
  14. package/dist/types/extensibility/gjc-plugins/tools.d.ts +8 -0
  15. package/dist/types/extensibility/gjc-plugins/types.d.ts +64 -0
  16. package/dist/types/extensibility/gjc-plugins/validation.d.ts +4 -0
  17. package/dist/types/extensibility/skills.d.ts +9 -1
  18. package/dist/types/gjc-runtime/state-runtime.d.ts +22 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -2
  20. package/dist/types/harness-control-plane/storage.d.ts +7 -0
  21. package/dist/types/lsp/client.d.ts +1 -0
  22. package/dist/types/modes/bridge/bridge-mode.d.ts +2 -0
  23. package/dist/types/modes/prompt-action-autocomplete.d.ts +2 -2
  24. package/dist/types/modes/rpc/rpc-client.d.ts +9 -1
  25. package/dist/types/modes/rpc/rpc-types.d.ts +179 -2
  26. package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
  27. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +16 -1
  28. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +47 -0
  29. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +7 -0
  30. package/dist/types/modes/shared/agent-wire/handshake.d.ts +11 -1
  31. package/dist/types/modes/shared/agent-wire/protocol.d.ts +3 -1
  32. package/dist/types/modes/shared/agent-wire/responses.d.ts +1 -1
  33. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +27 -0
  34. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
  35. package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +161 -0
  36. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +61 -0
  37. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +114 -0
  38. package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
  39. package/dist/types/modes/theme/theme.d.ts +2 -1
  40. package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
  41. package/dist/types/sdk.d.ts +7 -0
  42. package/dist/types/session/agent-session.d.ts +10 -0
  43. package/dist/types/session/blob-store.d.ts +17 -0
  44. package/dist/types/session/messages.d.ts +3 -0
  45. package/dist/types/session/session-storage.d.ts +6 -0
  46. package/dist/types/skill-state/active-state.d.ts +13 -0
  47. package/dist/types/thinking.d.ts +3 -2
  48. package/dist/types/tools/index.d.ts +3 -0
  49. package/package.json +9 -7
  50. package/src/cli.ts +14 -0
  51. package/src/commands/harness.ts +192 -7
  52. package/src/commands/ultragoal.ts +1 -21
  53. package/src/config/model-equivalence.ts +1 -1
  54. package/src/config/model-registry.ts +32 -5
  55. package/src/config/models-config-schema.ts +7 -2
  56. package/src/config/settings-schema.ts +4 -1
  57. package/src/discovery/claude-plugins.ts +25 -5
  58. package/src/edit/diff.ts +64 -1
  59. package/src/edit/modes/replace.ts +60 -2
  60. package/src/extensibility/gjc-plugins/activation.ts +87 -0
  61. package/src/extensibility/gjc-plugins/index.ts +9 -0
  62. package/src/extensibility/gjc-plugins/injection.ts +114 -0
  63. package/src/extensibility/gjc-plugins/loader.ts +131 -0
  64. package/src/extensibility/gjc-plugins/paths.ts +66 -0
  65. package/src/extensibility/gjc-plugins/schema.ts +79 -0
  66. package/src/extensibility/gjc-plugins/state.ts +29 -0
  67. package/src/extensibility/gjc-plugins/tools.ts +47 -0
  68. package/src/extensibility/gjc-plugins/types.ts +97 -0
  69. package/src/extensibility/gjc-plugins/validation.ts +76 -0
  70. package/src/extensibility/skills.ts +39 -7
  71. package/src/gjc-runtime/state-runtime.ts +93 -2
  72. package/src/gjc-runtime/state-writer.ts +17 -1
  73. package/src/gjc-runtime/ultragoal-runtime.ts +76 -121
  74. package/src/gjc-runtime/workflow-manifest.generated.json +5 -0
  75. package/src/gjc-runtime/workflow-manifest.ts +2 -2
  76. package/src/harness-control-plane/storage.ts +144 -2
  77. package/src/hashline/hash.ts +23 -0
  78. package/src/hooks/skill-state.ts +2 -0
  79. package/src/internal-urls/docs-index.generated.ts +5 -5
  80. package/src/lsp/client.ts +7 -0
  81. package/src/modes/acp/acp-agent.ts +25 -2
  82. package/src/modes/bridge/bridge-mode.ts +124 -2
  83. package/src/modes/controllers/input-controller.ts +14 -2
  84. package/src/modes/prompt-action-autocomplete.ts +49 -10
  85. package/src/modes/rpc/rpc-client.ts +57 -3
  86. package/src/modes/rpc/rpc-mode.ts +67 -0
  87. package/src/modes/rpc/rpc-types.ts +224 -2
  88. package/src/modes/shared/agent-wire/approval-gate.ts +151 -0
  89. package/src/modes/shared/agent-wire/command-dispatch.ts +97 -4
  90. package/src/modes/shared/agent-wire/command-validation.ts +25 -1
  91. package/src/modes/shared/agent-wire/deep-interview-gate.ts +222 -0
  92. package/src/modes/shared/agent-wire/event-envelope.ts +13 -0
  93. package/src/modes/shared/agent-wire/handshake.ts +43 -3
  94. package/src/modes/shared/agent-wire/protocol.ts +7 -0
  95. package/src/modes/shared/agent-wire/responses.ts +2 -2
  96. package/src/modes/shared/agent-wire/scopes.ts +2 -0
  97. package/src/modes/shared/agent-wire/unattended-action-policy.ts +341 -0
  98. package/src/modes/shared/agent-wire/unattended-audit.ts +175 -0
  99. package/src/modes/shared/agent-wire/unattended-run-controller.ts +406 -0
  100. package/src/modes/shared/agent-wire/unattended-session.ts +180 -0
  101. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +324 -0
  102. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +331 -0
  103. package/src/modes/theme/theme.ts +6 -0
  104. package/src/runtime-mcp/client.ts +7 -4
  105. package/src/runtime-mcp/manager.ts +45 -13
  106. package/src/runtime-mcp/transports/http.ts +40 -14
  107. package/src/runtime-mcp/transports/stdio.ts +11 -10
  108. package/src/sdk.ts +47 -0
  109. package/src/session/agent-session.ts +211 -2
  110. package/src/session/blob-store.ts +84 -0
  111. package/src/session/messages.ts +3 -0
  112. package/src/session/session-manager.ts +390 -33
  113. package/src/session/session-storage.ts +26 -0
  114. package/src/setup/provider-onboarding.ts +2 -2
  115. package/src/skill-state/active-state.ts +89 -1
  116. package/src/task/discovery.ts +7 -1
  117. package/src/task/executor.ts +16 -2
  118. package/src/thinking.ts +8 -2
  119. package/src/tools/ask.ts +39 -9
  120. package/src/tools/index.ts +3 -0
  121. package/src/tools/skill.ts +15 -3
  122. package/src/utils/edit-mode.ts +1 -1
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Deep-interview gate mapping (#316).
3
+ *
4
+ * Converts deep-interview `ask`-tool questions into machine-addressable
5
+ * `workflow_gate` { kind: "question" } events (option set + free-text shape
6
+ * encoded in `schema`/`options`) and decodes a `workflow_gate_response` answer
7
+ * back into the exact QuestionResult shape the human path produces, so ambiguity
8
+ * scoring/state updates proceed identically whether a human or an agent answers.
9
+ *
10
+ * This is the pure mapping primitive. Routing the ask tool through it (instead of
11
+ * the interactive select/editor UI) when an unattended controller + gate broker
12
+ * are attached is wired with the transport in #321 and exercised by #323.
13
+ */
14
+ import type { RpcJsonSchema } from "../../rpc/rpc-types";
15
+ import type { OpenGateInput } from "./workflow-gate-broker";
16
+
17
+ /** "Other (type your own)" sentinel, mirroring the interactive ask tool. */
18
+ export const GATE_OTHER_OPTION = "Other (type your own)";
19
+
20
+ export interface AskGateQuestion {
21
+ id: string;
22
+ question: string;
23
+ options: Array<{ label: string }>;
24
+ multi?: boolean;
25
+ recommended?: number;
26
+ }
27
+
28
+ export interface AskGateResult {
29
+ id: string;
30
+ question: string;
31
+ options: string[];
32
+ multi: boolean;
33
+ selectedOptions: string[];
34
+ customInput?: string;
35
+ }
36
+
37
+ /**
38
+ * The answer shape an agent returns for a deep-interview question gate.
39
+ *
40
+ * `selected` are picked option labels; free text is conveyed by `other: true`
41
+ * plus `custom`, encoded separately from `selected` so a real option whose label
42
+ * happens to equal the display sentinel can never collide with the free-text path.
43
+ */
44
+ export interface DeepInterviewGateAnswer {
45
+ selected: string[];
46
+ other?: boolean;
47
+ custom?: string;
48
+ }
49
+
50
+ export class DeepInterviewGateError extends Error {
51
+ constructor(
52
+ readonly code:
53
+ | "invalid_answer_shape"
54
+ | "unknown_option"
55
+ | "multi_not_allowed"
56
+ | "missing_custom"
57
+ | "empty_selection"
58
+ | "duplicate_selection",
59
+ message: string,
60
+ ) {
61
+ super(message);
62
+ this.name = "DeepInterviewGateError";
63
+ }
64
+ }
65
+
66
+ function deepInterviewQuestionState(questionText: string): Record<string, unknown> {
67
+ const roundMatch = /^Round\s+(\d+)\s+\|\s+([^|]+?)\s+\|\s+Ambiguity:\s*(.+?)\s*$/im.exec(questionText);
68
+ const state: Record<string, unknown> = {};
69
+ if (roundMatch) {
70
+ const round = Number(roundMatch[1]);
71
+ if (Number.isFinite(round)) state.round = round;
72
+ const mode = roundMatch[2]?.trim();
73
+ if (mode) {
74
+ state.mode = mode;
75
+ const normalized = mode.toLowerCase();
76
+ if (normalized.includes("topology")) state.topology_gate = true;
77
+ if (/(contrarian|simplifier|ontologist)/u.test(normalized)) state.challenge_mode = normalized;
78
+ }
79
+ const ambiguity = roundMatch[3]?.trim();
80
+ if (ambiguity) state.ambiguity = ambiguity;
81
+ }
82
+ if (/Round\s+0\s+\|\s+Topology confirmation/im.test(questionText)) {
83
+ state.round = 0;
84
+ state.mode = "Topology confirmation";
85
+ state.topology_gate = true;
86
+ }
87
+ return state;
88
+ }
89
+
90
+ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJsonSchema {
91
+ const multi = question.multi ?? false;
92
+ const selectedItems: RpcJsonSchema = { type: "string", enum: labels };
93
+ const selectedBase: RpcJsonSchema = { type: "array", items: selectedItems, uniqueItems: true };
94
+ const selectedOnly: RpcJsonSchema = {
95
+ ...selectedBase,
96
+ minItems: 1,
97
+ ...(multi ? {} : { maxItems: 1 }),
98
+ };
99
+ const selectedWithOther: RpcJsonSchema = {
100
+ ...selectedBase,
101
+ ...(multi ? {} : { maxItems: 0 }),
102
+ };
103
+ return {
104
+ type: "object",
105
+ properties: {
106
+ selected: selectedBase,
107
+ other: { type: "boolean", description: "set true to provide a free-text answer in `custom`" },
108
+ custom: { type: "string", minLength: 1, description: "free-text answer; required when `other` is true" },
109
+ },
110
+ required: ["selected"],
111
+ additionalProperties: false,
112
+ anyOf: [
113
+ {
114
+ type: "object",
115
+ properties: { selected: selectedOnly, other: { const: false } },
116
+ required: ["selected"],
117
+ additionalProperties: false,
118
+ },
119
+ {
120
+ type: "object",
121
+ properties: {
122
+ selected: selectedWithOther,
123
+ other: { const: true },
124
+ custom: { type: "string", minLength: 1 },
125
+ },
126
+ required: ["selected", "other", "custom"],
127
+ additionalProperties: false,
128
+ },
129
+ ],
130
+ };
131
+ }
132
+
133
+ /** Build the `workflow_gate` open-input for one deep-interview question. */
134
+ export function questionToGate(question: AskGateQuestion): OpenGateInput {
135
+ const labels = question.options.map(o => o.label);
136
+ const schema = questionAnswerSchema(question, labels);
137
+ return {
138
+ stage: "deep-interview",
139
+ kind: "question",
140
+ schema,
141
+ options: question.options.map((o, i) => ({
142
+ value: o.label,
143
+ label: o.label,
144
+ description: i === question.recommended ? "recommended" : undefined,
145
+ })),
146
+ context: {
147
+ title: question.question,
148
+ prompt: question.question,
149
+ stage_state: {
150
+ question_id: question.id,
151
+ multi: question.multi ?? false,
152
+ options: labels,
153
+ other_option: GATE_OTHER_OPTION,
154
+ ...deepInterviewQuestionState(question.question),
155
+ },
156
+ },
157
+ };
158
+ }
159
+
160
+ function isAnswer(value: unknown): value is DeepInterviewGateAnswer {
161
+ if (typeof value !== "object" || value === null) return false;
162
+ const v = value as DeepInterviewGateAnswer;
163
+ return (
164
+ Array.isArray(v.selected) &&
165
+ v.selected.every(s => typeof s === "string") &&
166
+ (v.other === undefined || typeof v.other === "boolean") &&
167
+ (v.custom === undefined || typeof v.custom === "string")
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Decode a gate answer into the QuestionResult the interactive path produces.
173
+ * Selections are de-duplicated (the interactive UI stores them in a Set), and
174
+ * free text is taken from `other`/`custom`. Throws DeepInterviewGateError on a
175
+ * semantically invalid answer.
176
+ */
177
+ export function gateAnswerToResult(question: AskGateQuestion, answer: unknown): AskGateResult {
178
+ if (!isAnswer(answer)) {
179
+ throw new DeepInterviewGateError(
180
+ "invalid_answer_shape",
181
+ "answer must be { selected: string[]; other?: boolean; custom?: string }",
182
+ );
183
+ }
184
+ const labels = question.options.map(o => o.label);
185
+ const multi = question.multi ?? false;
186
+ const valid = new Set(labels);
187
+ for (const sel of answer.selected) {
188
+ if (!valid.has(sel)) throw new DeepInterviewGateError("unknown_option", `unknown option: ${sel}`);
189
+ }
190
+ // Mirror the interactive UI, which stores selections in a Set (no duplicates).
191
+ const deduped = [...new Set(answer.selected)];
192
+ if (deduped.length !== answer.selected.length) {
193
+ throw new DeepInterviewGateError("duplicate_selection", "selected options must be unique");
194
+ }
195
+ const other = answer.other === true;
196
+ const totalPicks = deduped.length + (other ? 1 : 0);
197
+ if (totalPicks === 0) {
198
+ throw new DeepInterviewGateError(
199
+ "empty_selection",
200
+ "at least one option (or the free-text other) must be selected",
201
+ );
202
+ }
203
+ if (!multi && totalPicks > 1) {
204
+ throw new DeepInterviewGateError("multi_not_allowed", "this question accepts a single selection");
205
+ }
206
+ if (other && (answer.custom === undefined || answer.custom.trim() === "")) {
207
+ throw new DeepInterviewGateError("missing_custom", "custom text is required when `other` is true");
208
+ }
209
+ return {
210
+ id: question.id,
211
+ question: question.question,
212
+ options: labels,
213
+ multi,
214
+ selectedOptions: deduped,
215
+ customInput: other ? answer.custom : undefined,
216
+ };
217
+ }
218
+
219
+ /** Convenience: map a batch of ask questions to gate open-inputs. */
220
+ export function questionsToGates(questions: AskGateQuestion[]): OpenGateInput[] {
221
+ return questions.map(questionToGate);
222
+ }
@@ -106,3 +106,16 @@ export function toBridgeEventFrame(event: AgentSessionEvent, sequencer: BridgeFr
106
106
  event,
107
107
  });
108
108
  }
109
+
110
+ /**
111
+ * Serialize a `workflow_gate` event into a sequenced wire frame (#321). The
112
+ * gate_id is stamped as the correlation id so the answer (posted to the
113
+ * ui-responses endpoint) can be matched, and the monotonic `seq` gives replay
114
+ * while `frame_id` + gate_id give idempotency.
115
+ */
116
+ export function toBridgeWorkflowGateFrame(
117
+ gate: import("../../rpc/rpc-types").RpcWorkflowGate,
118
+ sequencer: BridgeFrameSequencer,
119
+ ): import("./protocol").BridgeWorkflowGateFrame {
120
+ return sequencer.next("workflow_gate", gate, gate.gate_id);
121
+ }
@@ -1,3 +1,4 @@
1
+ import type { RpcUnattendedDeclaration } from "../../rpc/rpc-types";
1
2
  import { BRIDGE_PROTOCOL_VERSION, type BridgeFrameType } from "./protocol";
2
3
  import type { BridgeCommandScope } from "./scopes";
3
4
 
@@ -13,7 +14,8 @@ export type BridgeCapability =
13
14
  | "host_uri"
14
15
  | "client_bridge.read_text_file"
15
16
  | "client_bridge.write_text_file"
16
- | "client_bridge.create_terminal";
17
+ | "client_bridge.create_terminal"
18
+ | "workflow_gate";
17
19
 
18
20
  export interface BridgeProtocolRange {
19
21
  min: number;
@@ -25,6 +27,8 @@ export interface BridgeHandshakeRequest {
25
27
  capabilities: BridgeCapability[];
26
28
  requested_scopes: BridgeCommandScope[];
27
29
  last_seq?: number;
30
+ /** Optional unattended declaration (budget + scope + action allowlist) for #318/#319. */
31
+ unattended?: RpcUnattendedDeclaration;
28
32
  }
29
33
 
30
34
  export interface BridgeEndpointDescriptor {
@@ -46,6 +50,10 @@ export interface BridgeHandshakeAccepted {
46
50
  unsupported: BridgeCapability[];
47
51
  endpoints: BridgeEndpointDescriptor;
48
52
  frame_types: BridgeFrameType[];
53
+ /** Echoed unattended declaration when one was supplied and accepted (#321). */
54
+ accepted_unattended?: RpcUnattendedDeclaration;
55
+ /** Server-side accepted unattended mode after live negotiation, not just declaration echo. */
56
+ unattended_active?: boolean;
49
57
  }
50
58
 
51
59
  export interface BridgeHandshakeRejected {
@@ -55,6 +63,29 @@ export interface BridgeHandshakeRejected {
55
63
  }
56
64
 
57
65
  export type BridgeHandshakeResponse = BridgeHandshakeAccepted | BridgeHandshakeRejected;
66
+
67
+ /** Shape-validate an optional unattended declaration carried on the handshake. */
68
+ export function isUnattendedDeclarationShape(value: unknown): value is RpcUnattendedDeclaration {
69
+ if (!value || typeof value !== "object") return false;
70
+ const d = value as Record<string, unknown>;
71
+ const b = d.budget as Record<string, unknown> | undefined;
72
+ const budgetOk =
73
+ !!b &&
74
+ typeof b === "object" &&
75
+ // Match UnattendedRunController fail-closed validation: positive finite budgets.
76
+ ["max_tokens", "max_tool_calls", "max_wall_time_ms", "max_cost_usd"].every(
77
+ k => typeof b[k] === "number" && Number.isFinite(b[k] as number) && (b[k] as number) > 0,
78
+ );
79
+ return (
80
+ typeof d.actor === "string" &&
81
+ d.actor.trim() !== "" &&
82
+ budgetOk &&
83
+ Array.isArray(d.scopes) &&
84
+ d.scopes.every(s => typeof s === "string") &&
85
+ Array.isArray(d.action_allowlist) &&
86
+ d.action_allowlist.every(s => typeof s === "string")
87
+ );
88
+ }
58
89
  export function isBridgeHandshakeRequest(value: unknown): value is BridgeHandshakeRequest {
59
90
  if (!value || typeof value !== "object") return false;
60
91
  const request = value as {
@@ -62,6 +93,7 @@ export function isBridgeHandshakeRequest(value: unknown): value is BridgeHandsha
62
93
  capabilities?: unknown;
63
94
  requested_scopes?: unknown;
64
95
  last_seq?: unknown;
96
+ unattended?: unknown;
65
97
  };
66
98
  const range = request.protocol_version_range as { min?: unknown; max?: unknown } | undefined;
67
99
  return (
@@ -74,7 +106,9 @@ export function isBridgeHandshakeRequest(value: unknown): value is BridgeHandsha
74
106
  request.capabilities.every(capability => typeof capability === "string") &&
75
107
  Array.isArray(request.requested_scopes) &&
76
108
  request.requested_scopes.every(scope => typeof scope === "string") &&
77
- (request.last_seq === undefined || (typeof request.last_seq === "number" && Number.isInteger(request.last_seq)))
109
+ (request.last_seq === undefined ||
110
+ (typeof request.last_seq === "number" && Number.isInteger(request.last_seq))) &&
111
+ (request.unattended === undefined || isUnattendedDeclarationShape(request.unattended))
78
112
  );
79
113
  }
80
114
 
@@ -86,6 +120,7 @@ export function negotiateBridgeHandshake(
86
120
  scopes: readonly BridgeCommandScope[];
87
121
  endpoints: BridgeEndpointDescriptor;
88
122
  frameTypes: readonly BridgeFrameType[];
123
+ acceptedUnattended?: RpcUnattendedDeclaration;
89
124
  },
90
125
  ): BridgeHandshakeResponse {
91
126
  if (
@@ -104,7 +139,7 @@ export function negotiateBridgeHandshake(
104
139
  const unsupported = request.capabilities.filter(capability => !acceptedSet.has(capability));
105
140
  const serverScopes = new Set(server.scopes);
106
141
  const acceptedScopes = request.requested_scopes.filter(scope => serverScopes.has(scope));
107
- return {
142
+ const accepted: BridgeHandshakeAccepted = {
108
143
  status: "accepted",
109
144
  protocol_version: BRIDGE_PROTOCOL_VERSION,
110
145
  session_id: server.sessionId,
@@ -114,4 +149,9 @@ export function negotiateBridgeHandshake(
114
149
  endpoints: server.endpoints,
115
150
  frame_types: [...server.frameTypes],
116
151
  };
152
+ if (server.acceptedUnattended !== undefined && acceptedSet.has("workflow_gate")) {
153
+ accepted.accepted_unattended = server.acceptedUnattended;
154
+ accepted.unattended_active = true;
155
+ }
156
+ return accepted;
117
157
  }
@@ -64,6 +64,7 @@ export type BridgeFrameType =
64
64
  | "host_tool_call"
65
65
  | "host_uri_request"
66
66
  | "reset"
67
+ | "workflow_gate"
67
68
  | "error";
68
69
 
69
70
  /**
@@ -94,3 +95,9 @@ export interface BridgeEventPayload {
94
95
 
95
96
  /** An `AgentSessionEvent` serialized into a versioned wire frame. */
96
97
  export type BridgeEventFrame = BridgeFrameEnvelope<"event", BridgeEventPayload>;
98
+
99
+ /** A `workflow_gate` event serialized into a versioned wire frame (#321). */
100
+ export type BridgeWorkflowGateFrame = BridgeFrameEnvelope<
101
+ "workflow_gate",
102
+ import("../../rpc/rpc-types").RpcWorkflowGate
103
+ >;
@@ -12,6 +12,6 @@ export function rpcSuccess<T extends RpcCommand["type"]>(
12
12
  return { id, type: "response", command, success: true, data } as RpcResponse;
13
13
  }
14
14
 
15
- export function rpcError(id: string | undefined, command: string, message: string): RpcResponse {
16
- return { id, type: "response", command, success: false, error: message };
15
+ export function rpcError(id: string | undefined, command: string, error: string | object): RpcResponse {
16
+ return { id, type: "response", command, success: false, error } as RpcResponse;
17
17
  }
@@ -71,6 +71,8 @@ const RPC_COMMAND_SCOPE_REGISTRY: Record<RpcCommandType, BridgeCommandScope> = {
71
71
  get_messages: "message:read",
72
72
  get_login_providers: "admin",
73
73
  login: "admin",
74
+ negotiate_unattended: "control",
75
+ workflow_gate_response: "prompt",
74
76
  };
75
77
 
76
78
  export const RPC_COMMAND_TYPES: readonly RpcCommandType[] = Object.keys(RPC_COMMAND_SCOPE_REGISTRY) as RpcCommandType[];