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.
Files changed (101) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -0
  3. package/api/README.md +2 -0
  4. package/dist/actions/actions.d.ts +17 -17
  5. package/dist/actions/actions.js +47 -34
  6. package/dist/actions/reads.d.ts +4 -4
  7. package/dist/actions/reads.js +16 -9
  8. package/dist/cli/cli.js +80 -21
  9. package/dist/contracts/constants.d.ts +9 -0
  10. package/dist/contracts/constants.js +10 -0
  11. package/dist/contracts/index.d.ts +1 -1
  12. package/dist/contracts/index.js +1 -1
  13. package/dist/errors/utilities.js +2 -2
  14. package/dist/handoffs/handoffs.d.ts +25 -25
  15. package/dist/handoffs/handoffs.js +93 -77
  16. package/dist/ipc/client.d.ts +13 -13
  17. package/dist/ipc/client.js +53 -25
  18. package/dist/ipc/framing.d.ts +1 -1
  19. package/dist/ipc/framing.js +15 -5
  20. package/dist/ipc/server.d.ts +1 -1
  21. package/dist/ipc/server.js +26 -8
  22. package/dist/locks/locks.d.ts +38 -38
  23. package/dist/locks/locks.js +99 -69
  24. package/dist/plans/admission.d.ts +36 -36
  25. package/dist/plans/admission.js +42 -39
  26. package/dist/plans/authority.d.ts +1 -1
  27. package/dist/plans/authority.js +7 -4
  28. package/dist/plans/evidence.d.ts +4 -4
  29. package/dist/plans/evidence.js +64 -29
  30. package/dist/plans/files.d.ts +8 -8
  31. package/dist/plans/files.js +59 -21
  32. package/dist/plans/finalization.d.ts +2 -2
  33. package/dist/plans/finalization.js +8 -5
  34. package/dist/plans/plans.d.ts +76 -76
  35. package/dist/plans/plans.js +91 -85
  36. package/dist/plans/projections.d.ts +2 -2
  37. package/dist/plans/projections.js +18 -7
  38. package/dist/plans/reconciliation.d.ts +3 -3
  39. package/dist/plans/reconciliation.js +13 -7
  40. package/dist/plans/revisions.d.ts +7 -7
  41. package/dist/plans/revisions.js +13 -10
  42. package/dist/plans/utilities.d.ts +2 -2
  43. package/dist/plans/utilities.js +36 -11
  44. package/dist/providers/claude/claude.d.ts +6 -6
  45. package/dist/providers/claude/claude.js +27 -14
  46. package/dist/providers/claude/control/control.d.ts +19 -19
  47. package/dist/providers/claude/control/control.js +45 -33
  48. package/dist/providers/claude/control/permissions.js +5 -2
  49. package/dist/providers/claude/control/skills.d.ts +1 -1
  50. package/dist/providers/claude/control/skills.js +12 -5
  51. package/dist/providers/claude/control/stream.d.ts +1 -1
  52. package/dist/providers/claude/control/stream.js +6 -3
  53. package/dist/providers/claude/history/history.model.d.ts +2 -2
  54. package/dist/providers/claude/history/history.model.js +30 -13
  55. package/dist/providers/claude/history/utilities.d.ts +3 -3
  56. package/dist/providers/claude/history/utilities.js +34 -16
  57. package/dist/providers/claude/rpc.d.ts +3 -3
  58. package/dist/providers/claude/rpc.js +56 -19
  59. package/dist/providers/claude/transformers.d.ts +1 -1
  60. package/dist/providers/claude/transformers.js +7 -4
  61. package/dist/providers/codex/approvals/utilities.d.ts +1 -1
  62. package/dist/providers/codex/approvals/utilities.js +6 -3
  63. package/dist/providers/codex/codex.d.ts +2 -2
  64. package/dist/providers/codex/codex.js +48 -21
  65. package/dist/providers/codex/rpc.d.ts +2 -2
  66. package/dist/providers/codex/rpc.js +60 -19
  67. package/dist/providers/codex/transformers.d.ts +4 -4
  68. package/dist/providers/codex/transformers.js +4 -4
  69. package/dist/providers/codex/types.d.ts +12 -12
  70. package/dist/providers/codex/validations.d.ts +1 -1
  71. package/dist/providers/codex/validations.js +7 -3
  72. package/dist/providers/process.d.ts +1 -1
  73. package/dist/providers/process.js +16 -7
  74. package/dist/providers/types.d.ts +3 -3
  75. package/dist/runtime/browser/browser.js +14 -5
  76. package/dist/runtime/runtime.d.ts +13 -13
  77. package/dist/runtime/runtime.js +49 -24
  78. package/dist/runtime/state/state.d.ts +18 -18
  79. package/dist/runtime/state/state.js +100 -45
  80. package/dist/runtime/types.d.ts +37 -37
  81. package/dist/sessions/sessions.d.ts +4 -4
  82. package/dist/sessions/sessions.js +15 -9
  83. package/dist/sessions/utilities.d.ts +1 -1
  84. package/dist/sessions/utilities.js +16 -6
  85. package/dist/skills/skills.d.ts +10 -10
  86. package/dist/skills/skills.js +27 -17
  87. package/dist/store/browser/browser.model.d.ts +1 -1
  88. package/dist/store/browser/browser.model.js +7 -4
  89. package/dist/store/control/control.model.d.ts +4 -4
  90. package/dist/store/control/control.model.js +10 -7
  91. package/dist/store/handoffs/handoffs.model.d.ts +3 -3
  92. package/dist/store/handoffs/handoffs.model.js +9 -6
  93. package/dist/store/plans/plans.model.d.ts +3 -3
  94. package/dist/store/plans/plans.model.js +29 -14
  95. package/dist/store/store.d.ts +1 -1
  96. package/dist/store/store.js +42 -13
  97. package/dist/store/store.worker.js +8 -4
  98. package/dist/store/types.d.ts +52 -51
  99. package/dist/store/types.js +3 -1
  100. package/errors/README.md +62 -62
  101. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ - Use `jg-exception` with named string codes for runtime, lock, plan, provider, client and tooling failures. Preserve codes through SQLite-worker responses and publish browser-safe client codes alongside their safe messages. This changes the Node error contract; callers should handle the documented named codes.
6
+ - Use the shared Astro UI 0.4.0, `jg-site` metadata helpers and `jg-playwright` browser checks. Remove `error-message-utils` from the runtime and website.
7
+ - Redesign the public website around Claude Code and Codex collaboration, a copyable API example, the available runtime capabilities and section navigation for the documentation.
8
+
3
9
  ## 0.5.2
4
10
 
5
11
  - Resume inactive managed Codex sessions retained as loaded by native unsubscribe, preserving explicit handoff and active/uncertain ownership guards.
package/README.md CHANGED
@@ -31,6 +31,8 @@ Short metadata operations use `withHqLock`. Long-lived runtime ownership must us
31
31
 
32
32
  See the [API reference](api/README.md) and [error contracts](errors/README.md).
33
33
 
34
+ Intentional failures use `Exception` from `jg-exception`, with the named string codes in the error reference. Local clients use `PUBLIC_ERROR_CODES` and `PUBLIC_ERROR_MESSAGES` from `jg-agent-hq/contracts` for errors translated at the IPC boundary. Safe classifications remain separate from private diagnostic causes.
35
+
34
36
  ## Local controller
35
37
 
36
38
  Install Codex and sign in through its native CLI, then start `jg-agent-hq serve --state-root .runtime --workspace-root /path/to/workspace`. Run `jg-agent-hq status --state-root .runtime` in a client. `threads`, `thread`, `turns`, `items` and `usage` read native information; `stop` waits for confirmed runtime shutdown.
package/api/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Import the named exports from `jg-agent-hq`. Internal module paths are private.
4
4
 
5
+ Intentional Node API failures use `Exception` from `jg-exception`. The [error reference](../errors/README.md) defines their named string codes and stable messages. `PUBLIC_ERROR_CODES` and `PUBLIC_ERROR_MESSAGES` are available from `jg-agent-hq/contracts` for the seven safe client classifications. SQLite-worker responses retain the originating application code; arbitrary worker failures become `JG_AGENT_HQ_STORE_FAILED`. Browser and IPC error bodies expose only their established safe classification and message.
6
+
5
7
  ## Lock lifecycle
6
8
 
7
9
  - `acquireHqLock(root, command, options?)` acquires `.runtime/hq.lock` and returns an asynchronous release function. The caller owns the trusted state root and supplies a non-empty, single-line operation label. Surrounding whitespace is trimmed. The returned release function is idempotent and refuses a replaced owner.
@@ -14,14 +14,14 @@ export declare const createActions: (native: ICodexProvider, claude: ReturnType<
14
14
  * Returns after durable admission; the native operation outlives this client request.
15
15
  * @returns The durable admission receipt after validation.
16
16
  * @throws
17
- * - -1: The HQ action conflicts with its recorded state
18
- * - -1: The HQ skill is unavailable
19
- * - -1: The HQ request is invalid
20
- * - -1: The HQ target is outside the allowed workspaces
21
- * - -1: The Codex integration failed
22
- * - -1: The Claude integration failed
23
- * - -1: The Claude history is unavailable
24
- * - -1: The HQ store failed
17
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
18
+ * - JG_AGENT_HQ_SKILL_UNAVAILABLE: The HQ skill is unavailable
19
+ * - JG_AGENT_HQ_REQUEST_INVALID: The HQ request is invalid
20
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
21
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
22
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
23
+ * - JG_AGENT_HQ_CLAUDE_HISTORY_UNAVAILABLE: The Claude history is unavailable
24
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
25
25
  */
26
26
  submit(action: IActionEnvelope): Promise<IActionReceipt>;
27
27
  /** Reads only current in-memory prompts for the selected managed thread. */
@@ -30,21 +30,21 @@ export declare const createActions: (native: ICodexProvider, claude: ReturnType<
30
30
  * Quiesces the selected managed cohort without stopping unrelated controllers.
31
31
  * @returns A promise resolved only after interruption, background cleanup and release evidence.
32
32
  * @throws
33
- * - -1: The HQ action conflicts with its recorded state
34
- * - -1: The HQ target is outside the allowed workspaces
35
- * - -1: The HQ store failed
36
- * - -1: The Codex integration failed
37
- * - -1: The Claude integration failed
33
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
34
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
35
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
36
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
37
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
38
38
  */
39
39
  quiescePlan(controls: IPlanRevisionWorkflow["controls"]): Promise<void>;
40
40
  /**
41
41
  * Stops admission, interrupts active work, cleans native terminals and confirms shutdown.
42
42
  * @returns A promise resolved after native exit and release of durable control ownership.
43
43
  * @throws
44
- * - -1: The Codex integration failed
45
- * - -1: The Claude integration failed
46
- * - -1: The HQ transport failed
47
- * - -1: The HQ store failed
44
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
45
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
46
+ * - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
47
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
48
48
  */
49
49
  close(): Promise<void>;
50
50
  };
@@ -1,5 +1,5 @@
1
1
  import { createHmac, randomUUID } from 'node:crypto';
2
- import { Exception, extractMessage } from 'error-message-utils';
2
+ import { Exception, extractMessage, isException } from 'jg-exception';
3
3
  import { z } from 'zod';
4
4
  import { setTimeout as wait } from 'node:timers/promises';
5
5
  import { getErrorKind } from '../errors/index.js';
@@ -40,12 +40,18 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
40
40
  let unavailable = false;
41
41
  let eventFailure;
42
42
  const conflict = () => {
43
- throw new Exception('The HQ action conflicts with its recorded state', undefined, {
44
- kind: 'state-conflict',
43
+ throw new Exception('The HQ action conflicts with its recorded state', {
44
+ code: 'JG_AGENT_HQ_ACTION_CONFLICT',
45
+ data: {
46
+ kind: 'state-conflict',
47
+ },
45
48
  });
46
49
  };
47
50
  const agentUnavailable = () => {
48
- throw new Exception('The Codex integration failed', undefined, { kind: 'agent-unavailable' });
51
+ throw new Exception('The Codex integration failed', {
52
+ code: 'JG_AGENT_HQ_CODEX_FAILED',
53
+ data: { kind: 'agent-unavailable' },
54
+ });
49
55
  };
50
56
  /** Quarantines controls after a lifecycle gap and stops the native transport. */
51
57
  const failEvents = (cause) => {
@@ -111,7 +117,10 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
111
117
  if (unavailable)
112
118
  return;
113
119
  if (eventCount >= 128) {
114
- failEvents(new Exception('The Codex integration failed', undefined, { kind: 'agent-unavailable' }));
120
+ failEvents(new Exception('The Codex integration failed', {
121
+ code: 'JG_AGENT_HQ_CODEX_FAILED',
122
+ data: { kind: 'agent-unavailable' },
123
+ }));
115
124
  return;
116
125
  }
117
126
  eventCount += 1;
@@ -179,8 +188,8 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
179
188
  * Dispatches one admitted intent once and records delivery without replaying it.
180
189
  * @returns A promise resolved after delivery evidence or uncertainty is persisted.
181
190
  * @throws
182
- * - -1: The HQ action conflicts with its recorded state
183
- * - -1: The HQ store failed
191
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
192
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
184
193
  */
185
194
  const dispatch = async (initial, originalThread, command, input) => {
186
195
  let receipt = initial;
@@ -295,7 +304,7 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
295
304
  }
296
305
  catch (error) {
297
306
  // Only a matching native error response proves rejection. Transport loss and malformed results remain uncertain.
298
- const explicit = error instanceof Exception && NativeRejectionSchema.safeParse(error.data).success;
307
+ const explicit = isException(error) && NativeRejectionSchema.safeParse(error.data).success;
299
308
  const kind = getErrorKind(error);
300
309
  const failure = kind === 'state-conflict' || kind === 'access-denied' || kind === 'agent-unavailable'
301
310
  ? kind
@@ -620,14 +629,14 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
620
629
  * Validates the current target and separates new dispatch from idempotent receipt reads.
621
630
  * @returns The durable admission receipt; native work runs independently.
622
631
  * @throws
623
- * - -1: The HQ action conflicts with its recorded state
624
- * - -1: The HQ skill is unavailable
625
- * - -1: The HQ request is invalid
626
- * - -1: The HQ target is outside the allowed workspaces
627
- * - -1: The Codex integration failed
628
- * - -1: The Claude integration failed
629
- * - -1: The Claude history is unavailable
630
- * - -1: The HQ store failed
632
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
633
+ * - JG_AGENT_HQ_SKILL_UNAVAILABLE: The HQ skill is unavailable
634
+ * - JG_AGENT_HQ_REQUEST_INVALID: The HQ request is invalid
635
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
636
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
637
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
638
+ * - JG_AGENT_HQ_CLAUDE_HISTORY_UNAVAILABLE: The Claude history is unavailable
639
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
631
640
  */
632
641
  const admitAndDispatch = async (action) => {
633
642
  // The worker verifies the complete digest before returning any existing receipt.
@@ -810,14 +819,14 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
810
819
  * Returns after durable admission; the native operation outlives this client request.
811
820
  * @returns The durable admission receipt after validation.
812
821
  * @throws
813
- * - -1: The HQ action conflicts with its recorded state
814
- * - -1: The HQ skill is unavailable
815
- * - -1: The HQ request is invalid
816
- * - -1: The HQ target is outside the allowed workspaces
817
- * - -1: The Codex integration failed
818
- * - -1: The Claude integration failed
819
- * - -1: The Claude history is unavailable
820
- * - -1: The HQ store failed
822
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
823
+ * - JG_AGENT_HQ_SKILL_UNAVAILABLE: The HQ skill is unavailable
824
+ * - JG_AGENT_HQ_REQUEST_INVALID: The HQ request is invalid
825
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
826
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
827
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
828
+ * - JG_AGENT_HQ_CLAUDE_HISTORY_UNAVAILABLE: The Claude history is unavailable
829
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
821
830
  */
822
831
  async submit(action) {
823
832
  const admission = admitAndDispatch(action);
@@ -839,11 +848,11 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
839
848
  * Quiesces the selected managed cohort without stopping unrelated controllers.
840
849
  * @returns A promise resolved only after interruption, background cleanup and release evidence.
841
850
  * @throws
842
- * - -1: The HQ action conflicts with its recorded state
843
- * - -1: The HQ target is outside the allowed workspaces
844
- * - -1: The HQ store failed
845
- * - -1: The Codex integration failed
846
- * - -1: The Claude integration failed
851
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
852
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
853
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
854
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
855
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
847
856
  */
848
857
  async quiescePlan(controls) {
849
858
  const failures = [];
@@ -860,10 +869,10 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
860
869
  * Stops admission, interrupts active work, cleans native terminals and confirms shutdown.
861
870
  * @returns A promise resolved after native exit and release of durable control ownership.
862
871
  * @throws
863
- * - -1: The Codex integration failed
864
- * - -1: The Claude integration failed
865
- * - -1: The HQ transport failed
866
- * - -1: The HQ store failed
872
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
873
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
874
+ * - JG_AGENT_HQ_TRANSPORT_FAILED: The HQ transport failed
875
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
867
876
  */
868
877
  async close() {
869
878
  closing = true;
@@ -902,7 +911,11 @@ export const createActions = (native, claude, readClaudeThread, store, roots, ep
902
911
  await lane.catch(() => undefined);
903
912
  const failedExit = nativeExit.find((result) => result.status === 'rejected');
904
913
  if (failedExit?.status === 'rejected')
905
- throw new Exception(`The HQ transport failed: ${extractMessage(failedExit.reason)}`, undefined, { kind: 'operation-failed' }, { cause: cleanupFailure ?? failedExit.reason });
914
+ throw new Exception(`The HQ transport failed: ${extractMessage(failedExit.reason)}`, {
915
+ code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
916
+ data: { kind: 'operation-failed' },
917
+ cause: cleanupFailure ?? failedExit.reason,
918
+ });
906
919
  }
907
920
  if (cleanupFailure)
908
921
  throw cleanupFailure;
@@ -8,10 +8,10 @@ type IControlRead = Extract<IHqRequest, {
8
8
  * Authorizes bounded control reads and binds seek positions to their exact query.
9
9
  * @returns The selected record or bounded page.
10
10
  * @throws
11
- * - -1: The HQ action conflicts with its recorded state
12
- * - -1: The HQ cursor does not match this request
13
- * - -1: The HQ target is outside the allowed workspaces
14
- * - -1: The HQ store failed
11
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
12
+ * - JG_AGENT_HQ_CURSOR_INVALID: The HQ cursor does not match this request
13
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
14
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
15
15
  */
16
16
  export declare const readControl: (command: IControlRead, store: IStore, actions: ReturnType<typeof createActions>, roots: string[]) => Promise<IHqResponse>;
17
17
  export {};
@@ -1,4 +1,4 @@
1
- import { Exception } from 'error-message-utils';
1
+ import { Exception } from 'jg-exception';
2
2
  import { z } from 'zod';
3
3
  import { resolveWorkspaceTarget } from '../runtime/state/index.js';
4
4
  import { decodeHistoryCursor, encodeHistoryCursor } from '../sessions/index.js';
@@ -6,7 +6,7 @@ const PositionSchema = z.strictObject({ createdAt: z.number().int(), id: z.uuid(
6
6
  /**
7
7
  * Validates the application-owned seek position after the shared query-binding check.
8
8
  * @throws
9
- * - -1: The HQ cursor does not match this request
9
+ * - JG_AGENT_HQ_CURSOR_INVALID: The HQ cursor does not match this request
10
10
  */
11
11
  const readPosition = (cursor, filters) => {
12
12
  const decoded = decodeHistoryCursor(cursor, filters);
@@ -16,7 +16,11 @@ const readPosition = (cursor, filters) => {
16
16
  return PositionSchema.parse(JSON.parse(decoded));
17
17
  }
18
18
  catch (cause) {
19
- throw new Exception('The HQ cursor does not match this request', undefined, { kind: 'invalid-request' }, { cause });
19
+ throw new Exception('The HQ cursor does not match this request', {
20
+ code: 'JG_AGENT_HQ_CURSOR_INVALID',
21
+ data: { kind: 'invalid-request' },
22
+ cause,
23
+ });
20
24
  }
21
25
  };
22
26
  /** Encodes the final row's complete ordering tuple only when another page exists. */
@@ -28,10 +32,10 @@ const nextPageCursor = (page, filters) => {
28
32
  * Authorizes bounded control reads and binds seek positions to their exact query.
29
33
  * @returns The selected record or bounded page.
30
34
  * @throws
31
- * - -1: The HQ action conflicts with its recorded state
32
- * - -1: The HQ cursor does not match this request
33
- * - -1: The HQ target is outside the allowed workspaces
34
- * - -1: The HQ store failed
35
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
36
+ * - JG_AGENT_HQ_CURSOR_INVALID: The HQ cursor does not match this request
37
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
38
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
35
39
  */
36
40
  export const readControl = async (command, store, actions, roots) => {
37
41
  const authorize = async (thread) => {
@@ -80,8 +84,11 @@ export const readControl = async (command, store, actions, roots) => {
80
84
  if (command.kind === 'managed-thread')
81
85
  return { kind: command.kind, result: thread };
82
86
  if (!thread)
83
- throw new Exception('The HQ action conflicts with its recorded state', undefined, {
84
- kind: 'state-conflict',
87
+ throw new Exception('The HQ action conflicts with its recorded state', {
88
+ code: 'JG_AGENT_HQ_ACTION_CONFLICT',
89
+ data: {
90
+ kind: 'state-conflict',
91
+ },
85
92
  });
86
93
  if (command.kind === 'events')
87
94
  return {
package/dist/cli/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { resolve } from 'node:path';
3
3
  import { parseArgs } from 'node:util';
4
4
  import { setTimeout as wait } from 'node:timers/promises';
5
- import { Exception, hasErrorCode } from 'error-message-utils';
5
+ import { Exception, hasErrorCode, isException } from 'jg-exception';
6
6
  import { HqRequestSchema } from '../contracts/index.js';
7
7
  import { toPublicError } from '../errors/index.js';
8
8
  import { requestAgentHq } from '../ipc/index.js';
@@ -40,20 +40,32 @@ const main = async () => {
40
40
  return;
41
41
  }
42
42
  if (positionals.length !== 1 || !values['state-root'])
43
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
43
+ throw new Exception('The HQ request is invalid', {
44
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
45
+ data: { kind: 'invalid-request' },
46
+ });
44
47
  const stateRoot = resolve(values['state-root']);
45
48
  const roots = (values['workspace-root'] ?? []).map((root) => resolve(root));
46
49
  if (kind === 'request') {
47
50
  if (Object.keys(values).some((key) => key !== 'state-root'))
48
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
51
+ throw new Exception('The HQ request is invalid', {
52
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
53
+ data: { kind: 'invalid-request' },
54
+ });
49
55
  const chunks = [];
50
56
  let bytes = 0;
51
57
  for await (const chunk of process.stdin) {
52
58
  if (!Buffer.isBuffer(chunk))
53
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
59
+ throw new Exception('The HQ request is invalid', {
60
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
61
+ data: { kind: 'invalid-request' },
62
+ });
54
63
  bytes += chunk.length;
55
64
  if (bytes > 65_536)
56
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
65
+ throw new Exception('The HQ request is invalid', {
66
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
67
+ data: { kind: 'invalid-request' },
68
+ });
57
69
  chunks.push(chunk);
58
70
  }
59
71
  let input;
@@ -61,11 +73,19 @@ const main = async () => {
61
73
  input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks, bytes)));
62
74
  }
63
75
  catch (cause) {
64
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' }, { cause });
76
+ throw new Exception('The HQ request is invalid', {
77
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
78
+ data: { kind: 'invalid-request' },
79
+ cause,
80
+ });
65
81
  }
66
82
  const parsed = HqRequestSchema.safeParse(input);
67
83
  if (!parsed.success)
68
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' }, { cause: parsed.error });
84
+ throw new Exception('The HQ request is invalid', {
85
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
86
+ data: { kind: 'invalid-request' },
87
+ cause: parsed.error,
88
+ });
69
89
  process.stdout.write(`${JSON.stringify({ schemaVersion: 1, ...(await requestAgentHq(stateRoot, parsed.data)) })}\n`);
70
90
  return;
71
91
  }
@@ -83,7 +103,10 @@ const main = async () => {
83
103
  values.limit ||
84
104
  values.search ||
85
105
  values.provider)
86
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
106
+ throw new Exception('The HQ request is invalid', {
107
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
108
+ data: { kind: 'invalid-request' },
109
+ });
87
110
  const runtime = await createAgentHq({
88
111
  stateRoot,
89
112
  coordinationRoot: values['coordination-root']
@@ -117,9 +140,15 @@ const main = async () => {
117
140
  if (values['codex-extra-skill-root'] ||
118
141
  values['claude-additional-directory'] ||
119
142
  values['coordination-root'])
120
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
143
+ throw new Exception('The HQ request is invalid', {
144
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
145
+ data: { kind: 'invalid-request' },
146
+ });
121
147
  if (roots.length > 1)
122
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
148
+ throw new Exception('The HQ request is invalid', {
149
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
150
+ data: { kind: 'invalid-request' },
151
+ });
123
152
  if (kind === 'plan-status' || kind === 'plan-milestones') {
124
153
  const allowed = new Set([
125
154
  'state-root',
@@ -127,7 +156,10 @@ const main = async () => {
127
156
  ...(kind === 'plan-milestones' ? ['limit', 'cursor'] : []),
128
157
  ]);
129
158
  if (!values.directory || Object.keys(values).some((key) => !allowed.has(key)))
130
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
159
+ throw new Exception('The HQ request is invalid', {
160
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
161
+ data: { kind: 'invalid-request' },
162
+ });
131
163
  const parsed = HqRequestSchema.safeParse({
132
164
  kind: 'plan',
133
165
  command: {
@@ -139,7 +171,11 @@ const main = async () => {
139
171
  },
140
172
  });
141
173
  if (!parsed.success)
142
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' }, { cause: parsed.error });
174
+ throw new Exception('The HQ request is invalid', {
175
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
176
+ data: { kind: 'invalid-request' },
177
+ cause: parsed.error,
178
+ });
143
179
  process.stdout.write(`${JSON.stringify({ schemaVersion: 1, ...(await requestAgentHq(stateRoot, parsed.data)) })}\n`);
144
180
  return;
145
181
  }
@@ -149,7 +185,10 @@ const main = async () => {
149
185
  ...(kind === 'handoff-show' ? ['id'] : ['workspace-root', 'thread-id', 'limit', 'cursor']),
150
186
  ]);
151
187
  if (Object.keys(values).some((key) => !allowed.has(key)))
152
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
188
+ throw new Exception('The HQ request is invalid', {
189
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
190
+ data: { kind: 'invalid-request' },
191
+ });
153
192
  const parsed = HqRequestSchema.safeParse({
154
193
  kind: 'handoff',
155
194
  command: kind === 'handoff-show'
@@ -163,14 +202,24 @@ const main = async () => {
163
202
  },
164
203
  });
165
204
  if (!parsed.success)
166
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' }, { cause: parsed.error });
205
+ throw new Exception('The HQ request is invalid', {
206
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
207
+ data: { kind: 'invalid-request' },
208
+ cause: parsed.error,
209
+ });
167
210
  process.stdout.write(`${JSON.stringify({ schemaVersion: 1, ...(await requestAgentHq(stateRoot, parsed.data)) })}\n`);
168
211
  return;
169
212
  }
170
213
  if (values.id)
171
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
214
+ throw new Exception('The HQ request is invalid', {
215
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
216
+ data: { kind: 'invalid-request' },
217
+ });
172
218
  if (values.directory)
173
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' });
219
+ throw new Exception('The HQ request is invalid', {
220
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
221
+ data: { kind: 'invalid-request' },
222
+ });
174
223
  const payload = Object.fromEntries([
175
224
  ['kind', kind],
176
225
  [
@@ -193,7 +242,11 @@ const main = async () => {
193
242
  ].filter(([, entry]) => entry !== undefined));
194
243
  const parsed = HqRequestSchema.safeParse(payload);
195
244
  if (!parsed.success)
196
- throw new Exception('The HQ request is invalid', undefined, { kind: 'invalid-request' }, { cause: parsed.error });
245
+ throw new Exception('The HQ request is invalid', {
246
+ code: 'JG_AGENT_HQ_REQUEST_INVALID',
247
+ data: { kind: 'invalid-request' },
248
+ cause: parsed.error,
249
+ });
197
250
  const command = parsed.data;
198
251
  const response = await requestAgentHq(stateRoot, command);
199
252
  if (response.kind === 'stop') {
@@ -203,12 +256,15 @@ const main = async () => {
203
256
  try {
204
257
  const { owner } = await readRuntimeOwner(stateRoot);
205
258
  if (owner.nonce !== response.result.runtimeEpoch)
206
- throw new Exception('The HQ runtime owner is unverifiable', undefined, {
207
- kind: 'state-conflict',
259
+ throw new Exception('The HQ runtime owner is unverifiable', {
260
+ code: 'JG_AGENT_HQ_RUNTIME_OWNER_UNVERIFIABLE',
261
+ data: {
262
+ kind: 'state-conflict',
263
+ },
208
264
  });
209
265
  }
210
266
  catch (error) {
211
- if (error instanceof Exception && hasErrorCode(error.cause, 'ENOENT')) {
267
+ if (isException(error) && hasErrorCode(error.cause, 'ENOENT')) {
212
268
  stopped = true;
213
269
  break;
214
270
  }
@@ -217,7 +273,10 @@ const main = async () => {
217
273
  await wait(100);
218
274
  }
219
275
  if (!stopped)
220
- throw new Exception('The HQ transport failed', undefined, { kind: 'state-conflict' });
276
+ throw new Exception('The HQ transport failed', {
277
+ code: 'JG_AGENT_HQ_TRANSPORT_FAILED',
278
+ data: { kind: 'state-conflict' },
279
+ });
221
280
  process.stdout.write(`${JSON.stringify({ schemaVersion: 1, kind: 'stop', result: { state: 'stopped', runtimeEpoch: response.result.runtimeEpoch } })}\n`);
222
281
  }
223
282
  else
@@ -1,5 +1,14 @@
1
1
  export declare const DEFAULT_PAGE_SIZE = 32;
2
2
  export declare const MAX_PAGE_SIZE = 128;
3
+ export declare const PUBLIC_ERROR_CODES: {
4
+ readonly 'invalid-request': "JG_AGENT_HQ_CLIENT_INVALID_REQUEST";
5
+ readonly 'authentication-required': "JG_AGENT_HQ_CLIENT_AUTHENTICATION_REQUIRED";
6
+ readonly 'access-denied': "JG_AGENT_HQ_CLIENT_ACCESS_DENIED";
7
+ readonly 'not-found': "JG_AGENT_HQ_CLIENT_NOT_FOUND";
8
+ readonly 'state-conflict': "JG_AGENT_HQ_CLIENT_STATE_CONFLICT";
9
+ readonly 'agent-unavailable': "JG_AGENT_HQ_CLIENT_AGENT_UNAVAILABLE";
10
+ readonly 'operation-failed': "JG_AGENT_HQ_CLIENT_OPERATION_FAILED";
11
+ };
3
12
  export declare const PUBLIC_ERROR_MESSAGES: {
4
13
  readonly 'invalid-request': "Request is invalid.";
5
14
  readonly 'authentication-required': "Sign in to continue.";
@@ -1,6 +1,16 @@
1
1
  // trusted-boundary page sizes for growing collections
2
2
  export const DEFAULT_PAGE_SIZE = 32;
3
3
  export const MAX_PAGE_SIZE = 128;
4
+ // stable client codes for failures translated at IPC and browser boundaries
5
+ export const PUBLIC_ERROR_CODES = {
6
+ 'invalid-request': 'JG_AGENT_HQ_CLIENT_INVALID_REQUEST',
7
+ 'authentication-required': 'JG_AGENT_HQ_CLIENT_AUTHENTICATION_REQUIRED',
8
+ 'access-denied': 'JG_AGENT_HQ_CLIENT_ACCESS_DENIED',
9
+ 'not-found': 'JG_AGENT_HQ_CLIENT_NOT_FOUND',
10
+ 'state-conflict': 'JG_AGENT_HQ_CLIENT_STATE_CONFLICT',
11
+ 'agent-unavailable': 'JG_AGENT_HQ_CLIENT_AGENT_UNAVAILABLE',
12
+ 'operation-failed': 'JG_AGENT_HQ_CLIENT_OPERATION_FAILED',
13
+ };
4
14
  // safe messages at IPC and browser boundaries
5
15
  export const PUBLIC_ERROR_MESSAGES = {
6
16
  'invalid-request': 'Request is invalid.',
@@ -1,4 +1,4 @@
1
- export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_MESSAGES } from './constants.js';
1
+ export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_CODES, PUBLIC_ERROR_MESSAGES, } from './constants.js';
2
2
  export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
3
3
  export type { IHqRequest, IHqRequestInput, IThreadSummary, ITurnSummary, IHistoryItem, IUsageSnapshot, IRuntimeStatus, IHqResponse, IErrorKind, IIpcResponse, } from './types.js';
4
4
  export { ActionCommandSchema, ActionEnvelopeSchema, ActionReceiptSchema, ActionSummarySchema, ManagedThreadSchema, NativeApprovalSchema, LifecycleEventSchema, } from './actions/index.js';
@@ -1,5 +1,5 @@
1
1
  // constants
2
- export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_MESSAGES } from './constants.js';
2
+ export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, PUBLIC_ERROR_CODES, PUBLIC_ERROR_MESSAGES, } from './constants.js';
3
3
  // wire contracts
4
4
  export { HqRequestSchema, ThreadSummarySchema, TurnSummarySchema, HistoryItemSchema, LiveItemsSchema, UsageWindowSchema, UsageSnapshotSchema, RuntimeStatusSchema, HqResponseSchema, ErrorKindSchema, IpcRequestSchema, IpcResponseSchema, } from './types.js';
5
5
  // managed control and durable delivery
@@ -1,10 +1,10 @@
1
- import { Exception } from 'error-message-utils';
1
+ import { isException } from 'jg-exception';
2
2
  import { z } from 'zod';
3
3
  import { ErrorKindSchema, PUBLIC_ERROR_MESSAGES } from '../contracts/index.js';
4
4
  const ErrorMetadataSchema = z.object({ kind: ErrorKindSchema });
5
5
  /** Reads only the supported classification from an application exception. */
6
6
  export const getErrorKind = (error) => {
7
- const metadata = ErrorMetadataSchema.safeParse(error instanceof Exception ? error.data : null);
7
+ const metadata = ErrorMetadataSchema.safeParse(isException(error) ? error.data : null);
8
8
  return metadata.success ? metadata.data.kind : 'operation-failed';
9
9
  };
10
10
  /** Maps recognized classifications to static client-safe messages. */
@@ -13,31 +13,31 @@ export declare const createHandoffs: (store: IStore, actions: ReturnType<typeof
13
13
  * Creates, previews, explicitly sends or collects one handoff without an automatic reply loop.
14
14
  * @returns A bounded handoff, native invocation preview or seek page.
15
15
  * @throws
16
- * - -1: The HQ handoff is unavailable
17
- * - -1: The HQ handoff operation failed
18
- * - -1: The HQ action conflicts with its recorded state
19
- * - -1: The HQ skill is unavailable
20
- * - -1: The HQ target is outside the allowed workspaces
21
- * - -1: The HQ cursor does not match this request
22
- * - -1: The HQ store failed
23
- * - -1: The Codex integration failed
24
- * - -1: The Claude integration failed
25
- * - -1: The Claude history is unavailable
26
- * - -1: The HQ plan is invalid
27
- * - -1: The HQ plan operation failed
28
- * - -1: The HQ lock acquisition arguments are invalid
29
- * - -1: The HQ lock requires Linux or macOS
30
- * - -1: The HQ runtime directory must be a real directory
31
- * - -1: The HQ runtime directory must be private and owned by the current user
32
- * - -1: The HQ lock must be a regular file
33
- * - -1: The HQ lock must be private and owned by the current user
34
- * - -1: The HQ lock owner exceeds the supported size
35
- * - -1: The HQ lock owner could not be read. Inspect .runtime/hq.lock
36
- * - -1: The HQ lock owner is incomplete. Inspect .runtime/hq.lock and remove it only after its owner stopped
37
- * - -1: The HQ lock has an inactive owner
38
- * - -1: The HQ lock owner could not be verified
39
- * - -1: Timed out waiting for the HQ lock
40
- * - -1: HQ lock ownership changed before release
16
+ * - JG_AGENT_HQ_HANDOFF_UNAVAILABLE: The HQ handoff is unavailable
17
+ * - JG_AGENT_HQ_HANDOFF_FAILED: The HQ handoff operation failed
18
+ * - JG_AGENT_HQ_ACTION_CONFLICT: The HQ action conflicts with its recorded state
19
+ * - JG_AGENT_HQ_SKILL_UNAVAILABLE: The HQ skill is unavailable
20
+ * - JG_AGENT_HQ_TARGET_FORBIDDEN: The HQ target is outside the allowed workspaces
21
+ * - JG_AGENT_HQ_CURSOR_INVALID: The HQ cursor does not match this request
22
+ * - JG_AGENT_HQ_STORE_FAILED: The HQ store failed
23
+ * - JG_AGENT_HQ_CODEX_FAILED: The Codex integration failed
24
+ * - JG_AGENT_HQ_CLAUDE_FAILED: The Claude integration failed
25
+ * - JG_AGENT_HQ_CLAUDE_HISTORY_UNAVAILABLE: The Claude history is unavailable
26
+ * - JG_AGENT_HQ_PLAN_INVALID: The HQ plan is invalid
27
+ * - JG_AGENT_HQ_PLAN_FAILED: The HQ plan operation failed
28
+ * - JG_AGENT_HQ_LOCK_INVALID_ARGUMENTS: The HQ lock acquisition arguments are invalid
29
+ * - JG_AGENT_HQ_LOCK_UNSUPPORTED_PLATFORM: The HQ lock requires Linux or macOS
30
+ * - JG_AGENT_HQ_RUNTIME_DIRECTORY_INVALID: The HQ runtime directory must be a real directory
31
+ * - JG_AGENT_HQ_RUNTIME_DIRECTORY_PERMISSIONS: The HQ runtime directory must be private and owned by the current user
32
+ * - JG_AGENT_HQ_LOCK_FILE_INVALID: The HQ lock must be a regular file
33
+ * - JG_AGENT_HQ_LOCK_FILE_PERMISSIONS: The HQ lock must be private and owned by the current user
34
+ * - JG_AGENT_HQ_LOCK_OWNER_OVERSIZED: The HQ lock owner exceeds the supported size
35
+ * - JG_AGENT_HQ_LOCK_OWNER_UNREADABLE: The HQ lock owner could not be read. Inspect .runtime/hq.lock
36
+ * - JG_AGENT_HQ_LOCK_OWNER_INCOMPLETE: The HQ lock owner is incomplete. Inspect .runtime/hq.lock and remove it only after its owner stopped
37
+ * - JG_AGENT_HQ_LOCK_OWNER_INACTIVE: The HQ lock has an inactive owner
38
+ * - JG_AGENT_HQ_LOCK_OWNER_UNVERIFIABLE: The HQ lock owner could not be verified
39
+ * - JG_AGENT_HQ_LOCK_TIMEOUT: Timed out waiting for the HQ lock
40
+ * - JG_AGENT_HQ_LOCK_OWNERSHIP_CHANGED: HQ lock ownership changed before release
41
41
  */
42
42
  handle(command: IHandoffCommand): Promise<IHandoffResult>;
43
43
  };