jg-agent-hq 0.5.2 → 1.0.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.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/api/README.md +2 -0
- package/dist/actions/actions.d.ts +17 -17
- package/dist/actions/actions.js +47 -34
- package/dist/actions/reads.d.ts +4 -4
- package/dist/actions/reads.js +16 -9
- package/dist/cli/cli.js +80 -21
- package/dist/contracts/constants.d.ts +9 -0
- package/dist/contracts/constants.js +10 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/index.js +1 -1
- package/dist/errors/utilities.js +2 -2
- package/dist/handoffs/handoffs.d.ts +25 -25
- package/dist/handoffs/handoffs.js +93 -77
- package/dist/ipc/client.d.ts +13 -13
- package/dist/ipc/client.js +53 -25
- package/dist/ipc/framing.d.ts +1 -1
- package/dist/ipc/framing.js +15 -5
- package/dist/ipc/server.d.ts +1 -1
- package/dist/ipc/server.js +26 -8
- package/dist/locks/locks.d.ts +38 -38
- package/dist/locks/locks.js +99 -69
- package/dist/plans/admission.d.ts +36 -36
- package/dist/plans/admission.js +42 -39
- package/dist/plans/authority.d.ts +1 -1
- package/dist/plans/authority.js +7 -4
- package/dist/plans/evidence.d.ts +4 -4
- package/dist/plans/evidence.js +64 -29
- package/dist/plans/files.d.ts +8 -8
- package/dist/plans/files.js +59 -21
- package/dist/plans/finalization.d.ts +2 -2
- package/dist/plans/finalization.js +8 -5
- package/dist/plans/plans.d.ts +76 -76
- package/dist/plans/plans.js +91 -85
- package/dist/plans/projections.d.ts +2 -2
- package/dist/plans/projections.js +18 -7
- package/dist/plans/reconciliation.d.ts +3 -3
- package/dist/plans/reconciliation.js +13 -7
- package/dist/plans/revisions.d.ts +7 -7
- package/dist/plans/revisions.js +13 -10
- package/dist/plans/utilities.d.ts +2 -2
- package/dist/plans/utilities.js +36 -11
- package/dist/providers/claude/claude.d.ts +6 -6
- package/dist/providers/claude/claude.js +27 -14
- package/dist/providers/claude/control/control.d.ts +19 -19
- package/dist/providers/claude/control/control.js +45 -33
- package/dist/providers/claude/control/permissions.js +5 -2
- package/dist/providers/claude/control/skills.d.ts +1 -1
- package/dist/providers/claude/control/skills.js +12 -5
- package/dist/providers/claude/control/stream.d.ts +1 -1
- package/dist/providers/claude/control/stream.js +6 -3
- package/dist/providers/claude/history/history.model.d.ts +2 -2
- package/dist/providers/claude/history/history.model.js +30 -13
- package/dist/providers/claude/history/utilities.d.ts +3 -3
- package/dist/providers/claude/history/utilities.js +34 -16
- package/dist/providers/claude/rpc.d.ts +3 -3
- package/dist/providers/claude/rpc.js +56 -19
- package/dist/providers/claude/transformers.d.ts +1 -1
- package/dist/providers/claude/transformers.js +7 -4
- package/dist/providers/codex/approvals/utilities.d.ts +1 -1
- package/dist/providers/codex/approvals/utilities.js +6 -3
- package/dist/providers/codex/codex.d.ts +2 -2
- package/dist/providers/codex/codex.js +48 -21
- package/dist/providers/codex/rpc.d.ts +2 -2
- package/dist/providers/codex/rpc.js +60 -19
- package/dist/providers/codex/transformers.d.ts +4 -4
- package/dist/providers/codex/transformers.js +4 -4
- package/dist/providers/codex/types.d.ts +12 -12
- package/dist/providers/codex/validations.d.ts +1 -1
- package/dist/providers/codex/validations.js +7 -3
- package/dist/providers/process.d.ts +1 -1
- package/dist/providers/process.js +16 -7
- package/dist/providers/types.d.ts +3 -3
- package/dist/runtime/browser/browser.js +14 -5
- package/dist/runtime/runtime.d.ts +13 -13
- package/dist/runtime/runtime.js +49 -24
- package/dist/runtime/state/state.d.ts +18 -18
- package/dist/runtime/state/state.js +100 -45
- package/dist/runtime/types.d.ts +37 -37
- package/dist/sessions/sessions.d.ts +4 -4
- package/dist/sessions/sessions.js +15 -9
- package/dist/sessions/utilities.d.ts +1 -1
- package/dist/sessions/utilities.js +16 -6
- package/dist/skills/skills.d.ts +10 -10
- package/dist/skills/skills.js +27 -17
- package/dist/store/browser/browser.model.d.ts +1 -1
- package/dist/store/browser/browser.model.js +7 -4
- package/dist/store/control/control.model.d.ts +4 -4
- package/dist/store/control/control.model.js +10 -7
- package/dist/store/handoffs/handoffs.model.d.ts +3 -3
- package/dist/store/handoffs/handoffs.model.js +9 -6
- package/dist/store/plans/plans.model.d.ts +3 -3
- package/dist/store/plans/plans.model.js +29 -14
- package/dist/store/store.d.ts +1 -1
- package/dist/store/store.js +42 -13
- package/dist/store/store.worker.js +8 -4
- package/dist/store/types.d.ts +52 -51
- package/dist/store/types.js +3 -1
- package/errors/README.md +62 -62
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
|
-
import { Exception, extractMessage } from '
|
|
4
|
+
import { Exception, extractMessage } from 'jg-exception';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { HandoffPreviewSchema, } from '../contracts/index.js';
|
|
7
7
|
import { resolveWorkspaceTarget } from '../runtime/state/index.js';
|
|
@@ -10,8 +10,11 @@ import { hashHandoff, handoffArguments, handoffDelivery } from './utilities.js';
|
|
|
10
10
|
const run = promisify(execFile);
|
|
11
11
|
const PositionSchema = z.strictObject({ createdAt: z.number().int(), id: z.uuid() });
|
|
12
12
|
const conflict = () => {
|
|
13
|
-
throw new Exception('The HQ action conflicts with its recorded state',
|
|
14
|
-
|
|
13
|
+
throw new Exception('The HQ action conflicts with its recorded state', {
|
|
14
|
+
code: 'JG_AGENT_HQ_ACTION_CONFLICT',
|
|
15
|
+
data: {
|
|
16
|
+
kind: 'state-conflict',
|
|
17
|
+
},
|
|
15
18
|
});
|
|
16
19
|
};
|
|
17
20
|
/**
|
|
@@ -23,7 +26,7 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
23
26
|
* Checks selected Git attribution without staging files or traversing history.
|
|
24
27
|
* @returns Whether the selected commit is still HEAD; omitted attribution requires no Git read.
|
|
25
28
|
* @throws
|
|
26
|
-
* -
|
|
29
|
+
* - JG_AGENT_HQ_HANDOFF_FAILED: The HQ handoff operation failed
|
|
27
30
|
*/
|
|
28
31
|
const checkCommit = async (directory, expected) => {
|
|
29
32
|
if (!expected)
|
|
@@ -38,21 +41,28 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
38
41
|
return result.stdout.trim() === expected;
|
|
39
42
|
}
|
|
40
43
|
catch (cause) {
|
|
41
|
-
throw new Exception(`The HQ handoff operation failed: ${extractMessage(cause)}`,
|
|
44
|
+
throw new Exception(`The HQ handoff operation failed: ${extractMessage(cause)}`, {
|
|
45
|
+
code: 'JG_AGENT_HQ_HANDOFF_FAILED',
|
|
46
|
+
data: { kind: 'operation-failed' },
|
|
47
|
+
cause,
|
|
48
|
+
});
|
|
42
49
|
}
|
|
43
50
|
};
|
|
44
51
|
/**
|
|
45
52
|
* Reads and authorizes a selected record before exposing its retained evidence.
|
|
46
53
|
* @returns The bounded persisted handoff.
|
|
47
54
|
* @throws
|
|
48
|
-
* -
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
55
|
+
* - JG_AGENT_HQ_HANDOFF_UNAVAILABLE: The HQ handoff is unavailable
|
|
56
|
+
* - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
|
|
57
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
51
58
|
*/
|
|
52
59
|
const selected = async (id) => {
|
|
53
60
|
const record = await store.getHandoff(id);
|
|
54
61
|
if (!record)
|
|
55
|
-
throw new Exception('The HQ handoff is unavailable',
|
|
62
|
+
throw new Exception('The HQ handoff is unavailable', {
|
|
63
|
+
code: 'JG_AGENT_HQ_HANDOFF_UNAVAILABLE',
|
|
64
|
+
data: { kind: 'not-found' },
|
|
65
|
+
});
|
|
56
66
|
await resolveWorkspaceTarget(record.source.workspaceRoot, roots);
|
|
57
67
|
return record;
|
|
58
68
|
};
|
|
@@ -60,7 +70,7 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
60
70
|
* Resolves the original native creation receipt; discovery never creates another controller.
|
|
61
71
|
* @returns The current control record, or null before creation admission.
|
|
62
72
|
* @throws
|
|
63
|
-
* -
|
|
73
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
64
74
|
*/
|
|
65
75
|
const target = async (record) => {
|
|
66
76
|
const creation = await store.getAction(record.creation.id);
|
|
@@ -70,7 +80,7 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
70
80
|
* Projects delivery from the action ledger, with no mirrored acknowledgement state.
|
|
71
81
|
* @returns Selected findings and current native receipts, preserving the original reviewer identity.
|
|
72
82
|
* @throws
|
|
73
|
-
* -
|
|
83
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
74
84
|
*/
|
|
75
85
|
const show = async (record) => {
|
|
76
86
|
const [creation, reviewDispatch, feedbackDispatch] = await Promise.all([
|
|
@@ -103,24 +113,24 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
103
113
|
* Checks that feedback still concerns the selected repository and execution contracts.
|
|
104
114
|
* @returns Whether source identity, selected commit and current contracts still match.
|
|
105
115
|
* @throws
|
|
106
|
-
* -
|
|
107
|
-
* -
|
|
108
|
-
* -
|
|
109
|
-
* -
|
|
110
|
-
* -
|
|
111
|
-
* -
|
|
112
|
-
* -
|
|
113
|
-
* -
|
|
114
|
-
* -
|
|
115
|
-
* -
|
|
116
|
-
* -
|
|
117
|
-
* -
|
|
118
|
-
* -
|
|
119
|
-
* -
|
|
120
|
-
* -
|
|
121
|
-
* -
|
|
122
|
-
* -
|
|
123
|
-
* -
|
|
116
|
+
* - JG_AGENT_HQ_HANDOFF_FAILED: The HQ handoff operation failed
|
|
117
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
118
|
+
* - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
|
|
119
|
+
* - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
|
|
120
|
+
* - JG_AGENT_HQ_PLAN_FAILED: The HQ plan operation failed
|
|
121
|
+
* - JG_AGENT_HQ_LOCK_INVALID_ARGUMENTS: The HQ lock acquisition arguments are invalid
|
|
122
|
+
* - JG_AGENT_HQ_LOCK_UNSUPPORTED_PLATFORM: The HQ lock requires Linux or macOS
|
|
123
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_INVALID: The HQ runtime directory must be a real directory
|
|
124
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_PERMISSIONS: The HQ runtime directory must be private and owned by the current user
|
|
125
|
+
* - JG_AGENT_HQ_LOCK_FILE_INVALID: The HQ lock must be a regular file
|
|
126
|
+
* - JG_AGENT_HQ_LOCK_FILE_PERMISSIONS: The HQ lock must be private and owned by the current user
|
|
127
|
+
* - JG_AGENT_HQ_LOCK_OWNER_OVERSIZED: The HQ lock owner exceeds the supported size
|
|
128
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNREADABLE: The HQ lock owner could not be read. Inspect .runtime/hq.lock
|
|
129
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INCOMPLETE: The HQ lock owner is incomplete. Inspect .runtime/hq.lock and remove it only after its owner stopped
|
|
130
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INACTIVE: The HQ lock has an inactive owner
|
|
131
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNVERIFIABLE: The HQ lock owner could not be verified
|
|
132
|
+
* - JG_AGENT_HQ_LOCK_TIMEOUT: Timed out waiting for the HQ lock
|
|
133
|
+
* - JG_AGENT_HQ_LOCK_OWNERSHIP_CHANGED: HQ lock ownership changed before release
|
|
124
134
|
*/
|
|
125
135
|
const isSourceCurrent = async (record) => {
|
|
126
136
|
const source = await store.getThread(record.source.threadId);
|
|
@@ -147,25 +157,25 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
147
157
|
* Resolves native skill metadata and exact invocation text without sending it.
|
|
148
158
|
* @returns A digest-bound preview with explicit unavailable states.
|
|
149
159
|
* @throws
|
|
150
|
-
* -
|
|
151
|
-
* -
|
|
152
|
-
* -
|
|
153
|
-
* -
|
|
154
|
-
* -
|
|
155
|
-
* -
|
|
156
|
-
* -
|
|
157
|
-
* -
|
|
158
|
-
* -
|
|
159
|
-
* -
|
|
160
|
-
* -
|
|
161
|
-
* -
|
|
162
|
-
* -
|
|
163
|
-
* -
|
|
164
|
-
* -
|
|
165
|
-
* -
|
|
166
|
-
* -
|
|
167
|
-
* -
|
|
168
|
-
* -
|
|
160
|
+
* - JG_AGENT_HQ_HANDOFF_FAILED: The HQ handoff operation failed
|
|
161
|
+
* - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
|
|
162
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
163
|
+
* - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
|
|
164
|
+
* - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
|
|
165
|
+
* - JG_AGENT_HQ_PLAN_FAILED: The HQ plan operation failed
|
|
166
|
+
* - JG_AGENT_HQ_LOCK_INVALID_ARGUMENTS: The HQ lock acquisition arguments are invalid
|
|
167
|
+
* - JG_AGENT_HQ_LOCK_UNSUPPORTED_PLATFORM: The HQ lock requires Linux or macOS
|
|
168
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_INVALID: The HQ runtime directory must be a real directory
|
|
169
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_PERMISSIONS: The HQ runtime directory must be private and owned by the current user
|
|
170
|
+
* - JG_AGENT_HQ_LOCK_FILE_INVALID: The HQ lock must be a regular file
|
|
171
|
+
* - JG_AGENT_HQ_LOCK_FILE_PERMISSIONS: The HQ lock must be private and owned by the current user
|
|
172
|
+
* - JG_AGENT_HQ_LOCK_OWNER_OVERSIZED: The HQ lock owner exceeds the supported size
|
|
173
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNREADABLE: The HQ lock owner could not be read. Inspect .runtime/hq.lock
|
|
174
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INCOMPLETE: The HQ lock owner is incomplete. Inspect .runtime/hq.lock and remove it only after its owner stopped
|
|
175
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INACTIVE: The HQ lock has an inactive owner
|
|
176
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNVERIFIABLE: The HQ lock owner could not be verified
|
|
177
|
+
* - JG_AGENT_HQ_LOCK_TIMEOUT: Timed out waiting for the HQ lock
|
|
178
|
+
* - JG_AGENT_HQ_LOCK_OWNERSHIP_CHANGED: HQ lock ownership changed before release
|
|
169
179
|
*/
|
|
170
180
|
const preview = async (record, phase) => {
|
|
171
181
|
const reviewer = await target(record);
|
|
@@ -226,31 +236,31 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
226
236
|
* Creates, previews, explicitly sends or collects one handoff without an automatic reply loop.
|
|
227
237
|
* @returns A bounded handoff, native invocation preview or seek page.
|
|
228
238
|
* @throws
|
|
229
|
-
* -
|
|
230
|
-
* -
|
|
231
|
-
* -
|
|
232
|
-
* -
|
|
233
|
-
* -
|
|
234
|
-
* -
|
|
235
|
-
* -
|
|
236
|
-
* -
|
|
237
|
-
* -
|
|
238
|
-
* -
|
|
239
|
-
* -
|
|
240
|
-
* -
|
|
241
|
-
* -
|
|
242
|
-
* -
|
|
243
|
-
* -
|
|
244
|
-
* -
|
|
245
|
-
* -
|
|
246
|
-
* -
|
|
247
|
-
* -
|
|
248
|
-
* -
|
|
249
|
-
* -
|
|
250
|
-
* -
|
|
251
|
-
* -
|
|
252
|
-
* -
|
|
253
|
-
* -
|
|
239
|
+
* - JG_AGENT_HQ_HANDOFF_UNAVAILABLE: The HQ handoff is unavailable
|
|
240
|
+
* - JG_AGENT_HQ_HANDOFF_FAILED: The HQ handoff operation failed
|
|
241
|
+
* - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
|
|
242
|
+
* - JG_AGENT_HQ_SKILL_UNAVAILABLE: The HQ skill is unavailable
|
|
243
|
+
* - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
|
|
244
|
+
* - JG_AGENT_HQ_CURSOR_INVALID: The HQ cursor does not match this request
|
|
245
|
+
* - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
|
|
246
|
+
* - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
|
|
247
|
+
* - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
|
|
248
|
+
* - JG_AGENT_HQ_CLAUDE_HISTORY_UNAVAILABLE: The Claude history is unavailable
|
|
249
|
+
* - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
|
|
250
|
+
* - JG_AGENT_HQ_PLAN_FAILED: The HQ plan operation failed
|
|
251
|
+
* - JG_AGENT_HQ_LOCK_INVALID_ARGUMENTS: The HQ lock acquisition arguments are invalid
|
|
252
|
+
* - JG_AGENT_HQ_LOCK_UNSUPPORTED_PLATFORM: The HQ lock requires Linux or macOS
|
|
253
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_INVALID: The HQ runtime directory must be a real directory
|
|
254
|
+
* - JG_AGENT_HQ_RUNTIME_DIRECTORY_PERMISSIONS: The HQ runtime directory must be private and owned by the current user
|
|
255
|
+
* - JG_AGENT_HQ_LOCK_FILE_INVALID: The HQ lock must be a regular file
|
|
256
|
+
* - JG_AGENT_HQ_LOCK_FILE_PERMISSIONS: The HQ lock must be private and owned by the current user
|
|
257
|
+
* - JG_AGENT_HQ_LOCK_OWNER_OVERSIZED: The HQ lock owner exceeds the supported size
|
|
258
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNREADABLE: The HQ lock owner could not be read. Inspect .runtime/hq.lock
|
|
259
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INCOMPLETE: The HQ lock owner is incomplete. Inspect .runtime/hq.lock and remove it only after its owner stopped
|
|
260
|
+
* - JG_AGENT_HQ_LOCK_OWNER_INACTIVE: The HQ lock has an inactive owner
|
|
261
|
+
* - JG_AGENT_HQ_LOCK_OWNER_UNVERIFIABLE: The HQ lock owner could not be verified
|
|
262
|
+
* - JG_AGENT_HQ_LOCK_TIMEOUT: Timed out waiting for the HQ lock
|
|
263
|
+
* - JG_AGENT_HQ_LOCK_OWNERSHIP_CHANGED: HQ lock ownership changed before release
|
|
254
264
|
*/
|
|
255
265
|
async handle(command) {
|
|
256
266
|
if (command.kind === 'list') {
|
|
@@ -264,14 +274,20 @@ export const createHandoffs = (store, actions, skills, plans, readHistory, roots
|
|
|
264
274
|
value = JSON.parse(continuation);
|
|
265
275
|
}
|
|
266
276
|
catch {
|
|
267
|
-
throw new Exception('The HQ cursor does not match this request',
|
|
268
|
-
|
|
277
|
+
throw new Exception('The HQ cursor does not match this request', {
|
|
278
|
+
code: 'JG_AGENT_HQ_CURSOR_INVALID',
|
|
279
|
+
data: {
|
|
280
|
+
kind: 'invalid-request',
|
|
281
|
+
},
|
|
269
282
|
});
|
|
270
283
|
}
|
|
271
284
|
const parsed = PositionSchema.safeParse(value);
|
|
272
285
|
if (!parsed.success)
|
|
273
|
-
throw new Exception('The HQ cursor does not match this request',
|
|
274
|
-
|
|
286
|
+
throw new Exception('The HQ cursor does not match this request', {
|
|
287
|
+
code: 'JG_AGENT_HQ_CURSOR_INVALID',
|
|
288
|
+
data: {
|
|
289
|
+
kind: 'invalid-request',
|
|
290
|
+
},
|
|
275
291
|
});
|
|
276
292
|
after = parsed.data;
|
|
277
293
|
}
|
package/dist/ipc/client.d.ts
CHANGED
|
@@ -3,18 +3,18 @@ import { type IHqRequestInput, type IHqResponse } from '../contracts/index.js';
|
|
|
3
3
|
* Runs one validated local command and closes only its client connection.
|
|
4
4
|
* @returns The matching normalized response; stopping requires subsequent shutdown confirmation.
|
|
5
5
|
* @throws
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
6
|
+
* - JG_AGENT_HQ_REQUEST_INVALID: The HQ request is invalid
|
|
7
|
+
* - JG_AGENT_HQ_STATE_DIRECTORY_UNSAFE: The HQ state directory is unsafe
|
|
8
|
+
* - JG_AGENT_HQ_STATE_FILE_UNSAFE: The HQ state file is unsafe
|
|
9
|
+
* - JG_AGENT_HQ_RUNTIME_OWNER_UNVERIFIABLE: The HQ runtime owner is unverifiable
|
|
10
|
+
* - JG_AGENT_HQ_RUNTIME_NOT_RUNNING: The HQ runtime is not running
|
|
11
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
12
|
+
* - JG_AGENT_HQ_CLIENT_INVALID_REQUEST: Request is invalid.
|
|
13
|
+
* - JG_AGENT_HQ_CLIENT_AUTHENTICATION_REQUIRED: Sign in to continue.
|
|
14
|
+
* - JG_AGENT_HQ_CLIENT_ACCESS_DENIED: Access is denied.
|
|
15
|
+
* - JG_AGENT_HQ_CLIENT_NOT_FOUND: Requested resource was not found.
|
|
16
|
+
* - JG_AGENT_HQ_CLIENT_STATE_CONFLICT: State changed. Refresh before continuing.
|
|
17
|
+
* - JG_AGENT_HQ_CLIENT_AGENT_UNAVAILABLE: Coding agent is unavailable.
|
|
18
|
+
* - JG_AGENT_HQ_CLIENT_OPERATION_FAILED: Operation could not be completed.
|
|
19
19
|
*/
|
|
20
20
|
export declare const requestAgentHq: (stateRoot: string, command: IHqRequestInput) => Promise<IHqResponse>;
|
package/dist/ipc/client.js
CHANGED
|
@@ -2,8 +2,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { createConnection } from 'node:net';
|
|
4
4
|
import { isAbsolute, join } from 'node:path';
|
|
5
|
-
import { Exception, extractMessage } from '
|
|
6
|
-
import { HqRequestSchema, IpcResponseSchema, PUBLIC_ERROR_MESSAGES, } from '../contracts/index.js';
|
|
5
|
+
import { Exception, extractMessage } from 'jg-exception';
|
|
6
|
+
import { HqRequestSchema, IpcResponseSchema, PUBLIC_ERROR_CODES, PUBLIC_ERROR_MESSAGES, } from '../contracts/index.js';
|
|
7
7
|
import { isPrivateOwner } from '../locks/index.js';
|
|
8
8
|
import { readRuntimeOwner } from '../runtime/state/index.js';
|
|
9
9
|
import { CONTROL_SOCKET, IPC_TIMEOUT_MS, MAX_FRAME_BYTES, MAX_REQUEST_BYTES } from './constants.js';
|
|
@@ -12,53 +12,78 @@ import { createLineDecoder } from './framing.js';
|
|
|
12
12
|
* Runs one validated local command and closes only its client connection.
|
|
13
13
|
* @returns The matching normalized response; stopping requires subsequent shutdown confirmation.
|
|
14
14
|
* @throws
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
21
|
-
* -
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
15
|
+
* - JG_AGENT_HQ_REQUEST_INVALID: The HQ request is invalid
|
|
16
|
+
* - JG_AGENT_HQ_STATE_DIRECTORY_UNSAFE: The HQ state directory is unsafe
|
|
17
|
+
* - JG_AGENT_HQ_STATE_FILE_UNSAFE: The HQ state file is unsafe
|
|
18
|
+
* - JG_AGENT_HQ_RUNTIME_OWNER_UNVERIFIABLE: The HQ runtime owner is unverifiable
|
|
19
|
+
* - JG_AGENT_HQ_RUNTIME_NOT_RUNNING: The HQ runtime is not running
|
|
20
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
21
|
+
* - JG_AGENT_HQ_CLIENT_INVALID_REQUEST: Request is invalid.
|
|
22
|
+
* - JG_AGENT_HQ_CLIENT_AUTHENTICATION_REQUIRED: Sign in to continue.
|
|
23
|
+
* - JG_AGENT_HQ_CLIENT_ACCESS_DENIED: Access is denied.
|
|
24
|
+
* - JG_AGENT_HQ_CLIENT_NOT_FOUND: Requested resource was not found.
|
|
25
|
+
* - JG_AGENT_HQ_CLIENT_STATE_CONFLICT: State changed. Refresh before continuing.
|
|
26
|
+
* - JG_AGENT_HQ_CLIENT_AGENT_UNAVAILABLE: Coding agent is unavailable.
|
|
27
|
+
* - JG_AGENT_HQ_CLIENT_OPERATION_FAILED: Operation could not be completed.
|
|
28
28
|
*/
|
|
29
29
|
export const requestAgentHq = async (stateRoot, command) => {
|
|
30
30
|
const input = HqRequestSchema.safeParse(command);
|
|
31
31
|
if (!input.success || !isAbsolute(stateRoot))
|
|
32
|
-
throw new Exception('The HQ request is invalid',
|
|
32
|
+
throw new Exception('The HQ request is invalid', {
|
|
33
|
+
code: 'JG_AGENT_HQ_REQUEST_INVALID',
|
|
34
|
+
data: { kind: 'invalid-request' },
|
|
35
|
+
});
|
|
33
36
|
const { owner } = await readRuntimeOwner(stateRoot);
|
|
34
37
|
const path = join(stateRoot, CONTROL_SOCKET);
|
|
35
38
|
const socketDetails = await lstat(path);
|
|
36
39
|
if (!socketDetails.isSocket() || socketDetails.isSymbolicLink() || !isPrivateOwner(socketDetails))
|
|
37
|
-
throw new Exception('The HQ state file is unsafe',
|
|
40
|
+
throw new Exception('The HQ state file is unsafe', {
|
|
41
|
+
code: 'JG_AGENT_HQ_STATE_FILE_UNSAFE',
|
|
42
|
+
data: { kind: 'state-conflict' },
|
|
43
|
+
});
|
|
38
44
|
const id = randomUUID();
|
|
39
45
|
const content = `${JSON.stringify({ schemaVersion: 1, id, runtimeEpoch: owner.nonce, command: input.data })}\n`;
|
|
40
46
|
if (Buffer.byteLength(content) > MAX_REQUEST_BYTES)
|
|
41
|
-
throw new Exception('The HQ request is invalid',
|
|
47
|
+
throw new Exception('The HQ request is invalid', {
|
|
48
|
+
code: 'JG_AGENT_HQ_REQUEST_INVALID',
|
|
49
|
+
data: { kind: 'invalid-request' },
|
|
50
|
+
});
|
|
42
51
|
const socket = createConnection(path);
|
|
43
52
|
try {
|
|
44
53
|
return await new Promise((resolve, reject) => {
|
|
45
|
-
socket.setTimeout(IPC_TIMEOUT_MS, () => reject(new Exception('The HQ transport failed',
|
|
46
|
-
|
|
54
|
+
socket.setTimeout(IPC_TIMEOUT_MS, () => reject(new Exception('The HQ transport failed', {
|
|
55
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
56
|
+
data: { kind: 'agent-unavailable' },
|
|
57
|
+
})));
|
|
58
|
+
socket.once('error', (cause) => reject(new Exception(`The HQ transport failed: ${extractMessage(cause)}`, {
|
|
59
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
60
|
+
data: { kind: 'agent-unavailable' },
|
|
61
|
+
cause,
|
|
62
|
+
})));
|
|
47
63
|
let received = false;
|
|
48
64
|
const decoder = createLineDecoder(MAX_FRAME_BYTES, (message) => {
|
|
49
65
|
const parsed = IpcResponseSchema.safeParse(message);
|
|
50
66
|
if (!parsed.success || parsed.data.id !== id)
|
|
51
|
-
throw new Exception('The HQ transport failed',
|
|
67
|
+
throw new Exception('The HQ transport failed', {
|
|
68
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
69
|
+
data: { kind: 'agent-unavailable' },
|
|
70
|
+
});
|
|
52
71
|
received = true;
|
|
53
72
|
const response = parsed.data;
|
|
54
73
|
if (!response.success) {
|
|
55
|
-
reject(new Exception(PUBLIC_ERROR_MESSAGES[response.error.kind],
|
|
56
|
-
|
|
74
|
+
reject(new Exception(PUBLIC_ERROR_MESSAGES[response.error.kind], {
|
|
75
|
+
code: PUBLIC_ERROR_CODES[response.error.kind],
|
|
76
|
+
data: {
|
|
77
|
+
kind: response.error.kind,
|
|
78
|
+
},
|
|
57
79
|
}));
|
|
58
80
|
return;
|
|
59
81
|
}
|
|
60
82
|
if (response.response.kind !== input.data.kind)
|
|
61
|
-
throw new Exception('The HQ transport failed',
|
|
83
|
+
throw new Exception('The HQ transport failed', {
|
|
84
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
85
|
+
data: { kind: 'agent-unavailable' },
|
|
86
|
+
});
|
|
62
87
|
resolve(response.response);
|
|
63
88
|
});
|
|
64
89
|
socket.on('data', (chunk) => {
|
|
@@ -73,7 +98,10 @@ export const requestAgentHq = async (stateRoot, command) => {
|
|
|
73
98
|
try {
|
|
74
99
|
decoder.end();
|
|
75
100
|
if (!received)
|
|
76
|
-
reject(new Exception('The HQ transport failed',
|
|
101
|
+
reject(new Exception('The HQ transport failed', {
|
|
102
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
103
|
+
data: { kind: 'agent-unavailable' },
|
|
104
|
+
}));
|
|
77
105
|
}
|
|
78
106
|
catch (error) {
|
|
79
107
|
reject(error);
|
package/dist/ipc/framing.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @param receive The owner of each decoded message.
|
|
5
5
|
* @returns A decoder whose end operation rejects incomplete tails.
|
|
6
6
|
* @throws
|
|
7
|
-
* -
|
|
7
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
8
8
|
*/
|
|
9
9
|
export declare const createLineDecoder: (maxBytes: number, receive: (message: unknown) => void) => {
|
|
10
10
|
push(chunk: Buffer): void;
|
package/dist/ipc/framing.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Exception } from '
|
|
1
|
+
import { Exception } from 'jg-exception';
|
|
2
2
|
/**
|
|
3
3
|
* Decodes bounded JSON lines with amortized linear copying across fragmented input.
|
|
4
4
|
* @param maxBytes Maximum bytes in one JSON frame.
|
|
5
5
|
* @param receive The owner of each decoded message.
|
|
6
6
|
* @returns A decoder whose end operation rejects incomplete tails.
|
|
7
7
|
* @throws
|
|
8
|
-
* -
|
|
8
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
9
9
|
*/
|
|
10
10
|
export const createLineDecoder = (maxBytes, receive) => {
|
|
11
11
|
let buffer = Buffer.alloc(Math.min(1024, maxBytes));
|
|
@@ -19,7 +19,10 @@ export const createLineDecoder = (maxBytes, receive) => {
|
|
|
19
19
|
const end = newline < 0 ? chunk.length : newline;
|
|
20
20
|
const length = end - offset;
|
|
21
21
|
if (used + length > maxBytes)
|
|
22
|
-
throw new Exception('The HQ transport failed',
|
|
22
|
+
throw new Exception('The HQ transport failed', {
|
|
23
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
24
|
+
data: { kind: 'invalid-request' },
|
|
25
|
+
});
|
|
23
26
|
if (used + length > buffer.length) {
|
|
24
27
|
const expanded = Buffer.alloc(Math.min(maxBytes, Math.max(buffer.length * 2, used + length)));
|
|
25
28
|
buffer.copy(expanded, 0, 0, used);
|
|
@@ -33,7 +36,11 @@ export const createLineDecoder = (maxBytes, receive) => {
|
|
|
33
36
|
message = JSON.parse(decoder.decode(buffer.subarray(0, used)));
|
|
34
37
|
}
|
|
35
38
|
catch (cause) {
|
|
36
|
-
throw new Exception('The HQ transport failed',
|
|
39
|
+
throw new Exception('The HQ transport failed', {
|
|
40
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
41
|
+
data: { kind: 'invalid-request' },
|
|
42
|
+
cause,
|
|
43
|
+
});
|
|
37
44
|
}
|
|
38
45
|
used = 0;
|
|
39
46
|
receive(message);
|
|
@@ -43,7 +50,10 @@ export const createLineDecoder = (maxBytes, receive) => {
|
|
|
43
50
|
},
|
|
44
51
|
end() {
|
|
45
52
|
if (used)
|
|
46
|
-
throw new Exception('The HQ transport failed',
|
|
53
|
+
throw new Exception('The HQ transport failed', {
|
|
54
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
55
|
+
data: { kind: 'invalid-request' },
|
|
56
|
+
});
|
|
47
57
|
},
|
|
48
58
|
};
|
|
49
59
|
};
|
package/dist/ipc/server.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type IHqRequest, type IHqResponse } from '../contracts/index.js';
|
|
|
3
3
|
* Serves closed HQ commands over an owner-only Unix socket. Disconnects do not own host lifetime.
|
|
4
4
|
* @returns An owned server with deterministic connection teardown.
|
|
5
5
|
* @throws
|
|
6
|
-
* -
|
|
6
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
7
7
|
*/
|
|
8
8
|
export declare const createIpcServer: (path: string, runtimeEpoch: string, handle: (command: IHqRequest) => Promise<IHqResponse>) => Promise<{
|
|
9
9
|
readonly isAvailable: boolean;
|
package/dist/ipc/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'node:net';
|
|
2
2
|
import { chmod } from 'node:fs/promises';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { Exception, extractMessage } from '
|
|
4
|
+
import { Exception, extractMessage } from 'jg-exception';
|
|
5
5
|
import { HqResponseSchema, IpcRequestSchema, PUBLIC_ERROR_MESSAGES, } from '../contracts/index.js';
|
|
6
6
|
import { toPublicError } from '../errors/index.js';
|
|
7
7
|
import { IPC_TIMEOUT_MS, MAX_CONNECTIONS, MAX_FRAME_BYTES, MAX_REQUEST_BYTES, } from './constants.js';
|
|
@@ -10,7 +10,7 @@ import { createLineDecoder } from './framing.js';
|
|
|
10
10
|
* Serves closed HQ commands over an owner-only Unix socket. Disconnects do not own host lifetime.
|
|
11
11
|
* @returns An owned server with deterministic connection teardown.
|
|
12
12
|
* @throws
|
|
13
|
-
* -
|
|
13
|
+
* - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
|
|
14
14
|
*/
|
|
15
15
|
export const createIpcServer = async (path, runtimeEpoch, handle) => {
|
|
16
16
|
const sockets = new Set();
|
|
@@ -36,14 +36,24 @@ export const createIpcServer = async (path, runtimeEpoch, handle) => {
|
|
|
36
36
|
const id = input.success ? input.data.id : randomUUID();
|
|
37
37
|
try {
|
|
38
38
|
if (!input.success)
|
|
39
|
-
throw new Exception('The HQ request is invalid',
|
|
39
|
+
throw new Exception('The HQ request is invalid', {
|
|
40
|
+
code: 'JG_AGENT_HQ_REQUEST_INVALID',
|
|
41
|
+
data: { kind: 'invalid-request' },
|
|
42
|
+
cause: input.error,
|
|
43
|
+
});
|
|
40
44
|
if (input.data.runtimeEpoch !== runtimeEpoch)
|
|
41
|
-
throw new Exception('The HQ runtime owner is unverifiable',
|
|
42
|
-
|
|
45
|
+
throw new Exception('The HQ runtime owner is unverifiable', {
|
|
46
|
+
code: 'JG_AGENT_HQ_RUNTIME_OWNER_UNVERIFIABLE',
|
|
47
|
+
data: {
|
|
48
|
+
kind: 'state-conflict',
|
|
49
|
+
},
|
|
43
50
|
});
|
|
44
51
|
const response = HqResponseSchema.safeParse(await handle(input.data.command));
|
|
45
52
|
if (!response.success || response.data.kind !== input.data.command.kind)
|
|
46
|
-
throw new Exception('The HQ transport failed',
|
|
53
|
+
throw new Exception('The HQ transport failed', {
|
|
54
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
55
|
+
data: { kind: 'operation-failed' },
|
|
56
|
+
});
|
|
47
57
|
send({ schemaVersion: 1, id, success: true, response: response.data });
|
|
48
58
|
}
|
|
49
59
|
catch (error) {
|
|
@@ -90,7 +100,11 @@ export const createIpcServer = async (path, runtimeEpoch, handle) => {
|
|
|
90
100
|
socket.destroy();
|
|
91
101
|
if (server.listening)
|
|
92
102
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
93
|
-
throw new Exception(`The HQ transport failed: ${extractMessage(cause)}`,
|
|
103
|
+
throw new Exception(`The HQ transport failed: ${extractMessage(cause)}`, {
|
|
104
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
105
|
+
data: { kind: 'operation-failed' },
|
|
106
|
+
cause,
|
|
107
|
+
});
|
|
94
108
|
}
|
|
95
109
|
let closing;
|
|
96
110
|
return {
|
|
@@ -101,7 +115,11 @@ export const createIpcServer = async (path, runtimeEpoch, handle) => {
|
|
|
101
115
|
for (const socket of sockets)
|
|
102
116
|
socket.destroy();
|
|
103
117
|
server.close((error) => error
|
|
104
|
-
? reject(new Exception(`The HQ transport failed: ${extractMessage(error)}`,
|
|
118
|
+
? reject(new Exception(`The HQ transport failed: ${extractMessage(error)}`, {
|
|
119
|
+
code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
|
|
120
|
+
data: { kind: 'operation-failed' },
|
|
121
|
+
cause: error,
|
|
122
|
+
}))
|
|
105
123
|
: resolve());
|
|
106
124
|
})),
|
|
107
125
|
};
|