@indigoai-us/hq-cli 5.108.1 → 5.108.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/assets/mesh-daemon/README.md +23 -0
  3. package/assets/mesh-daemon/launchd.plist.template +33 -0
  4. package/assets/mesh-daemon/systemd.system.service.template +27 -0
  5. package/assets/mesh-daemon/systemd.user.service.template +18 -0
  6. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +267 -0
  7. package/dist/commands/core.js +5 -4
  8. package/dist/commands/mesh.d.ts +15 -0
  9. package/dist/commands/mesh.js +732 -4
  10. package/dist/commands/search.d.ts +23 -0
  11. package/dist/commands/search.js +42 -1
  12. package/dist/lib/doctor/checks/work-context.d.ts +23 -0
  13. package/dist/lib/doctor/checks/work-context.js +195 -0
  14. package/dist/lib/doctor/registry.js +4 -0
  15. package/dist/lib/mesh/api.d.ts +18 -0
  16. package/dist/lib/mesh/api.js +55 -0
  17. package/dist/lib/mesh/cache.js +35 -4
  18. package/dist/lib/mesh/client.d.ts +118 -0
  19. package/dist/lib/mesh/client.js +268 -0
  20. package/dist/lib/mesh/live/backoff.d.ts +16 -0
  21. package/dist/lib/mesh/live/backoff.js +20 -0
  22. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +49 -0
  23. package/dist/lib/mesh/live/daemon/board-refresh.js +185 -0
  24. package/dist/lib/mesh/live/daemon/credentials.d.ts +71 -0
  25. package/dist/lib/mesh/live/daemon/credentials.js +227 -0
  26. package/dist/lib/mesh/live/daemon/doctor.d.ts +37 -0
  27. package/dist/lib/mesh/live/daemon/doctor.js +139 -0
  28. package/dist/lib/mesh/live/daemon/index.d.ts +24 -0
  29. package/dist/lib/mesh/live/daemon/index.js +14 -0
  30. package/dist/lib/mesh/live/daemon/install.d.ts +83 -0
  31. package/dist/lib/mesh/live/daemon/install.js +330 -0
  32. package/dist/lib/mesh/live/daemon/log.d.ts +11 -0
  33. package/dist/lib/mesh/live/daemon/log.js +79 -0
  34. package/dist/lib/mesh/live/daemon/paths.d.ts +18 -0
  35. package/dist/lib/mesh/live/daemon/paths.js +31 -0
  36. package/dist/lib/mesh/live/daemon/pid-lock.d.ts +48 -0
  37. package/dist/lib/mesh/live/daemon/pid-lock.js +134 -0
  38. package/dist/lib/mesh/live/daemon/presence.d.ts +101 -0
  39. package/dist/lib/mesh/live/daemon/presence.js +250 -0
  40. package/dist/lib/mesh/live/daemon/presign.d.ts +18 -0
  41. package/dist/lib/mesh/live/daemon/presign.js +65 -0
  42. package/dist/lib/mesh/live/daemon/run.d.ts +74 -0
  43. package/dist/lib/mesh/live/daemon/run.js +400 -0
  44. package/dist/lib/mesh/live/daemon/state.d.ts +24 -0
  45. package/dist/lib/mesh/live/daemon/state.js +57 -0
  46. package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +170 -0
  47. package/dist/lib/mesh/live/daemon/transcript-watch.js +520 -0
  48. package/dist/lib/mesh/live/enqueue.d.ts +48 -0
  49. package/dist/lib/mesh/live/enqueue.js +100 -0
  50. package/dist/lib/mesh/live/flush.d.ts +56 -0
  51. package/dist/lib/mesh/live/flush.js +360 -0
  52. package/dist/lib/mesh/live/format-spool-line.d.ts +42 -0
  53. package/dist/lib/mesh/live/format-spool-line.js +106 -0
  54. package/dist/lib/mesh/live/index.d.ts +22 -0
  55. package/dist/lib/mesh/live/index.js +16 -0
  56. package/dist/lib/mesh/live/paths.d.ts +13 -0
  57. package/dist/lib/mesh/live/paths.js +30 -0
  58. package/dist/lib/mesh/live/session-events-client.d.ts +58 -0
  59. package/dist/lib/mesh/live/session-events-client.js +120 -0
  60. package/dist/lib/mesh/live/session-identity.d.ts +39 -0
  61. package/dist/lib/mesh/live/session-identity.js +91 -0
  62. package/dist/lib/mesh/live/spool.d.ts +52 -0
  63. package/dist/lib/mesh/live/spool.js +193 -0
  64. package/dist/lib/mesh/live/ulid.d.ts +12 -0
  65. package/dist/lib/mesh/live/ulid.js +39 -0
  66. package/dist/lib/mesh/live/validate-session-event.d.ts +39 -0
  67. package/dist/lib/mesh/live/validate-session-event.js +128 -0
  68. package/dist/lib/search-index/index.d.ts +45 -0
  69. package/dist/lib/search-index/index.js +76 -0
  70. package/dist/lib/work-context/atomic.d.ts +18 -0
  71. package/dist/lib/work-context/atomic.js +132 -0
  72. package/dist/lib/work-context/company.d.ts +103 -0
  73. package/dist/lib/work-context/company.js +274 -0
  74. package/dist/lib/work-context/config.d.ts +59 -0
  75. package/dist/lib/work-context/config.js +172 -0
  76. package/dist/lib/work-context/contract.d.ts +81 -0
  77. package/dist/lib/work-context/contract.js +65 -0
  78. package/dist/lib/work-context/errors.d.ts +27 -0
  79. package/dist/lib/work-context/errors.js +46 -0
  80. package/dist/lib/work-context/index.d.ts +23 -0
  81. package/dist/lib/work-context/index.js +20 -0
  82. package/dist/lib/work-context/migrate.d.ts +84 -0
  83. package/dist/lib/work-context/migrate.js +287 -0
  84. package/dist/lib/work-context/organize.d.ts +130 -0
  85. package/dist/lib/work-context/organize.js +471 -0
  86. package/dist/lib/work-context/outbox.d.ts +97 -0
  87. package/dist/lib/work-context/outbox.js +357 -0
  88. package/dist/lib/work-context/paths.d.ts +14 -0
  89. package/dist/lib/work-context/paths.js +39 -0
  90. package/dist/lib/work-context/project.d.ts +78 -0
  91. package/dist/lib/work-context/project.js +247 -0
  92. package/dist/lib/work-context/reconcile.d.ts +85 -0
  93. package/dist/lib/work-context/reconcile.js +651 -0
  94. package/dist/lib/work-context/repo-remote.d.ts +43 -0
  95. package/dist/lib/work-context/repo-remote.js +200 -0
  96. package/dist/lib/work-context/state.d.ts +112 -0
  97. package/dist/lib/work-context/state.js +162 -0
  98. package/dist/main.js +40 -3
  99. package/dist/utils/cognito-session.d.ts +85 -9
  100. package/dist/utils/cognito-session.js +216 -9
  101. package/dist/utils/qmd-model-download-error.d.ts +27 -0
  102. package/dist/utils/qmd-model-download-error.js +114 -0
  103. package/dist/utils/qmd-query-document-error.d.ts +23 -0
  104. package/dist/utils/qmd-query-document-error.js +117 -0
  105. package/dist/utils/sentry-fingerprint.js +2 -0
  106. package/package.json +5 -1
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Typed Work Context errors (US-007A / resolver error map).
3
+ */
4
+ export class WorkContextError extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "WorkContextError";
9
+ this.code = code;
10
+ }
11
+ }
12
+ export class NotTrackingError extends WorkContextError {
13
+ constructor(message, code = "NOT_TRACKING") {
14
+ super(code, message);
15
+ this.name = "NotTrackingError";
16
+ }
17
+ }
18
+ export class DefaultCompanyLockedError extends WorkContextError {
19
+ constructor(message = "Default-company mode is locked until the server advertises migrationCapability") {
20
+ super("DEFAULT_COMPANY_LOCKED", message);
21
+ this.name = "DefaultCompanyLockedError";
22
+ }
23
+ }
24
+ export class DefaultCompanyUnavailableError extends WorkContextError {
25
+ constructor(message = "Cannot set default company: membership unavailable or caller is not a current member") {
26
+ super("DEFAULT_COMPANY_UNAVAILABLE", message);
27
+ this.name = "DefaultCompanyUnavailableError";
28
+ }
29
+ }
30
+ export class InvalidDecisionOriginError extends WorkContextError {
31
+ constructor(message = "Untracked origin must be user; model/agent origins are rejected") {
32
+ super("InvalidDecisionOriginError", message);
33
+ this.name = "InvalidDecisionOriginError";
34
+ }
35
+ }
36
+ export class UnsafeConfigPathError extends WorkContextError {
37
+ constructor(message) {
38
+ super("UnsafeConfigPathError", message);
39
+ this.name = "UnsafeConfigPathError";
40
+ }
41
+ }
42
+ /** Exit codes for `hq mesh context reconcile` machine mode. */
43
+ export const EXIT_OK = 0;
44
+ export const EXIT_INVALID_IDENTITY = 2;
45
+ export const EXIT_NOT_TRACKING = 3;
46
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Work Context reconciliation package (consumable by hq-cli).
3
+ * Contract (US-001) + reconcile / outbox / device default (US-007A)
4
+ * + organize / project resolution (US-007B)
5
+ * + cross-company correct / migrate (US-017B).
6
+ */
7
+ export * from "./contract.js";
8
+ export * from "./errors.js";
9
+ export * from "./paths.js";
10
+ export * from "./atomic.js";
11
+ export * from "./config.js";
12
+ export * from "./state.js";
13
+ export * from "./outbox.js";
14
+ export * from "./company.js";
15
+ export * from "./project.js";
16
+ export { decisionFromCandidates, digestOrganizePayload, formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, stableDecisionId, stableOrganizeOperationId, submitOrganizeDecision, } from "./organize.js";
17
+ export type { CandidatesResponse, FetchCandidatesFn, OrganizeCandidate, OrganizeListResult, OrganizeReceipt, OrganizeSubmitDecision, OrganizeSubmitResult, PostOrganizeFn, } from "./organize.js";
18
+ export { digestMigratePayload, formatMigrateConfirmation, stableMigrateOperationId, submitSessionMigration, } from "./migrate.js";
19
+ export type { MigrateDestination, MigrateReceipt, MigrateSubmitResult, PostMigrateFn, } from "./migrate.js";
20
+ export { deriveRemoteOwnerSlug, matchCompanySlugForRepo, normalizeRemoteOwnerName, } from "./repo-remote.js";
21
+ export { loadObservationFromFile, markUntracked, parseObservationJson, reconcileObservation, } from "./reconcile.js";
22
+ export type { ReconcileDeps, ReconcileObservation, ReconcileOutcome, } from "./reconcile.js";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Work Context reconciliation package (consumable by hq-cli).
3
+ * Contract (US-001) + reconcile / outbox / device default (US-007A)
4
+ * + organize / project resolution (US-007B)
5
+ * + cross-company correct / migrate (US-017B).
6
+ */
7
+ export * from "./contract.js";
8
+ export * from "./errors.js";
9
+ export * from "./paths.js";
10
+ export * from "./atomic.js";
11
+ export * from "./config.js";
12
+ export * from "./state.js";
13
+ export * from "./outbox.js";
14
+ export * from "./company.js";
15
+ export * from "./project.js";
16
+ export { decisionFromCandidates, digestOrganizePayload, formatOrganizeList, prepareOrganizeDecision, settleOrganizeAskWithoutBind, stableDecisionId, stableOrganizeOperationId, submitOrganizeDecision, } from "./organize.js";
17
+ export { digestMigratePayload, formatMigrateConfirmation, stableMigrateOperationId, submitSessionMigration, } from "./migrate.js";
18
+ export { deriveRemoteOwnerSlug, matchCompanySlugForRepo, normalizeRemoteOwnerName, } from "./repo-remote.js";
19
+ export { loadObservationFromFile, markUntracked, parseObservationJson, reconcileObservation, } from "./reconcile.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Cross-company session correction (US-017B).
3
+ *
4
+ * Submits only a structured migrate request (operationId + digest), tracks the
5
+ * receipt via the outbox, and keeps the local state file at migration_pending
6
+ * until the destination receipt arrives. Never rebinds by any other path.
7
+ */
8
+ import { type OutboxOperation } from "./outbox.js";
9
+ import { type SessionStateFile } from "./state.js";
10
+ export interface MigrateDestination {
11
+ projectId?: string;
12
+ taskId?: string;
13
+ }
14
+ export interface MigrateReceipt {
15
+ sessionId: string;
16
+ sourceCompanyUid: string;
17
+ destinationCompanyUid: string;
18
+ destination: MigrateDestination;
19
+ eventCount: number;
20
+ bindingCount: number;
21
+ receiptCount: number;
22
+ version: number;
23
+ migratedAt: string;
24
+ receipt: {
25
+ operationId: string;
26
+ digest: string;
27
+ status: "committed" | "replayed";
28
+ };
29
+ }
30
+ export interface MigrateSubmitResult {
31
+ ok: boolean;
32
+ /** True when confirmation was declined — nothing was sent. */
33
+ cancelled?: boolean;
34
+ pending?: boolean;
35
+ receipt?: MigrateReceipt;
36
+ outbox?: OutboxOperation;
37
+ state?: SessionStateFile;
38
+ confirmationSummary?: string;
39
+ errorCode?: string;
40
+ message?: string;
41
+ }
42
+ export type PostMigrateFn = (input: {
43
+ sessionId: string;
44
+ operationId: string;
45
+ digest: string;
46
+ sourceCompanyUid: string;
47
+ destinationCompanyUid: string;
48
+ destination: MigrateDestination;
49
+ expectedVersion: number;
50
+ }) => Promise<MigrateReceipt>;
51
+ /** Canonical digest material — mirrors hq-pro migrateDigestPayload + sha256. */
52
+ export declare function digestMigratePayload(parts: {
53
+ sessionId: string;
54
+ sourceCompanyUid: string;
55
+ destinationCompanyUid: string;
56
+ destination: MigrateDestination;
57
+ expectedVersion: number;
58
+ }): string;
59
+ export declare function stableMigrateOperationId(sessionId: string, sourceCompanyUid: string, destinationCompanyUid: string): string;
60
+ /** Human-readable summary printed before the confirmation gate. */
61
+ export declare function formatMigrateConfirmation(input: {
62
+ sessionId: string;
63
+ sourceCompanySlug?: string;
64
+ sourceCompanyUid: string;
65
+ destinationCompanySlug: string;
66
+ destinationCompanyUid: string;
67
+ destination: MigrateDestination;
68
+ }): string;
69
+ /**
70
+ * Submit a cross-company correction after the caller has already confirmed.
71
+ * Writes migration_pending + outbox before any network I/O; on destination
72
+ * receipt, rewrites the state file to the destination company.
73
+ */
74
+ export declare function submitSessionMigration(input: {
75
+ sessionId: string;
76
+ root: string;
77
+ destinationCompanyUid: string;
78
+ destinationCompanySlug: string;
79
+ destination?: MigrateDestination;
80
+ expectedVersion?: number;
81
+ now?: () => Date;
82
+ postMigrate: PostMigrateFn;
83
+ }): Promise<MigrateSubmitResult>;
84
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Cross-company session correction (US-017B).
3
+ *
4
+ * Submits only a structured migrate request (operationId + digest), tracks the
5
+ * receipt via the outbox, and keeps the local state file at migration_pending
6
+ * until the destination receipt arrives. Never rebinds by any other path.
7
+ */
8
+ import * as crypto from "node:crypto";
9
+ import { WORK_CONTEXT_CONTRACT_VERSION } from "./contract.js";
10
+ import { NotTrackingError } from "./errors.js";
11
+ import { enqueueOutbox, markOutboxAcked, markOutboxQuarantined, markOutboxQueued, } from "./outbox.js";
12
+ import { mergeLocalSessionFields, readSessionState, writeSessionState, } from "./state.js";
13
+ /** Canonical digest material — mirrors hq-pro migrateDigestPayload + sha256. */
14
+ export function digestMigratePayload(parts) {
15
+ const canonical = JSON.stringify({
16
+ sessionId: parts.sessionId,
17
+ sourceCompanyUid: parts.sourceCompanyUid,
18
+ destinationCompanyUid: parts.destinationCompanyUid,
19
+ destination: {
20
+ ...(parts.destination.projectId
21
+ ? { projectId: parts.destination.projectId }
22
+ : {}),
23
+ ...(parts.destination.taskId ? { taskId: parts.destination.taskId } : {}),
24
+ },
25
+ expectedVersion: parts.expectedVersion,
26
+ });
27
+ return crypto.createHash("sha256").update(canonical).digest("hex");
28
+ }
29
+ export function stableMigrateOperationId(sessionId, sourceCompanyUid, destinationCompanyUid) {
30
+ const h = crypto
31
+ .createHash("sha256")
32
+ .update(`migrate:v1:${sessionId}:${sourceCompanyUid}:${destinationCompanyUid}`)
33
+ .digest("hex")
34
+ .slice(0, 32);
35
+ return `op_${h}`;
36
+ }
37
+ /** Human-readable summary printed before the confirmation gate. */
38
+ export function formatMigrateConfirmation(input) {
39
+ const from = input.sourceCompanySlug && input.sourceCompanySlug !== input.sourceCompanyUid
40
+ ? `${input.sourceCompanySlug} (${input.sourceCompanyUid})`
41
+ : input.sourceCompanyUid;
42
+ const to = input.destinationCompanySlug !== input.destinationCompanyUid
43
+ ? `${input.destinationCompanySlug} (${input.destinationCompanyUid})`
44
+ : input.destinationCompanyUid;
45
+ const destBits = [];
46
+ if (input.destination.projectId) {
47
+ destBits.push(`project=${input.destination.projectId}`);
48
+ }
49
+ if (input.destination.taskId) {
50
+ destBits.push(`task=${input.destination.taskId}`);
51
+ }
52
+ const destExtra = destBits.length ? ` (${destBits.join(", ")})` : "";
53
+ return [
54
+ `Move session ${input.sessionId}`,
55
+ ` from company: ${from}`,
56
+ ` to company: ${to}${destExtra}`,
57
+ "This freezes the source, copies history to the destination, and leaves a tombstone. It does not move any project or task.",
58
+ ].join("\n");
59
+ }
60
+ /**
61
+ * Submit a cross-company correction after the caller has already confirmed.
62
+ * Writes migration_pending + outbox before any network I/O; on destination
63
+ * receipt, rewrites the state file to the destination company.
64
+ */
65
+ export async function submitSessionMigration(input) {
66
+ const state = readSessionState(input.sessionId, input.root);
67
+ if (!state) {
68
+ return {
69
+ ok: false,
70
+ errorCode: "NO_STATE",
71
+ message: `No work-context state for session ${input.sessionId}`,
72
+ };
73
+ }
74
+ if (!state.companyUid) {
75
+ return {
76
+ ok: false,
77
+ errorCode: "SOURCE_COMPANY_REQUIRED",
78
+ message: "Session has no verified source companyUid; cannot migrate across companies",
79
+ };
80
+ }
81
+ if (state.companyUid === input.destinationCompanyUid) {
82
+ return {
83
+ ok: false,
84
+ errorCode: "SAME_COMPANY",
85
+ message: "Destination company must differ from the session's current company",
86
+ };
87
+ }
88
+ if (state.contextStatus === "untracked") {
89
+ return {
90
+ ok: false,
91
+ errorCode: "UNTRACKED",
92
+ message: "Untracked sessions have no server history to migrate",
93
+ };
94
+ }
95
+ const destination = {};
96
+ if (input.destination?.projectId?.trim()) {
97
+ destination.projectId = input.destination.projectId.trim();
98
+ }
99
+ if (input.destination?.taskId?.trim()) {
100
+ destination.taskId = input.destination.taskId.trim();
101
+ }
102
+ if (destination.taskId && !destination.projectId) {
103
+ return {
104
+ ok: false,
105
+ errorCode: "PROJECT_REQUIRED",
106
+ message: "destination projectId is required when taskId is set",
107
+ };
108
+ }
109
+ const expectedVersion = typeof input.expectedVersion === "number" &&
110
+ Number.isInteger(input.expectedVersion) &&
111
+ input.expectedVersion >= 0
112
+ ? input.expectedVersion
113
+ : typeof state.version === "number"
114
+ ? state.version
115
+ : 0;
116
+ const operationId = stableMigrateOperationId(input.sessionId, state.companyUid, input.destinationCompanyUid);
117
+ const digest = digestMigratePayload({
118
+ sessionId: input.sessionId,
119
+ sourceCompanyUid: state.companyUid,
120
+ destinationCompanyUid: input.destinationCompanyUid,
121
+ destination,
122
+ expectedVersion,
123
+ });
124
+ const confirmationSummary = formatMigrateConfirmation({
125
+ sessionId: input.sessionId,
126
+ sourceCompanySlug: state.companySlug,
127
+ sourceCompanyUid: state.companyUid,
128
+ destinationCompanySlug: input.destinationCompanySlug,
129
+ destinationCompanyUid: input.destinationCompanyUid,
130
+ destination,
131
+ });
132
+ const nowFn = input.now ?? (() => new Date());
133
+ const nowIso = nowFn().toISOString();
134
+ let outbox;
135
+ try {
136
+ outbox = enqueueOutbox({
137
+ clientOperationId: operationId,
138
+ sessionId: input.sessionId,
139
+ companyUid: state.companyUid,
140
+ companySlug: state.companySlug,
141
+ projectId: destination.projectId,
142
+ taskId: destination.taskId,
143
+ kind: "migrate",
144
+ destinationCompanyUid: input.destinationCompanyUid,
145
+ destinationCompanySlug: input.destinationCompanySlug,
146
+ now: nowFn,
147
+ }, input.root);
148
+ }
149
+ catch (err) {
150
+ if (err instanceof NotTrackingError) {
151
+ return { ok: false, errorCode: "NOT_TRACKING", message: err.message };
152
+ }
153
+ throw err;
154
+ }
155
+ // Freeze local view before network I/O.
156
+ const pendingState = mergeLocalSessionFields({
157
+ contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
158
+ sessionId: input.sessionId,
159
+ companyUid: state.companyUid,
160
+ companySlug: state.companySlug,
161
+ contextStatus: "migration_pending",
162
+ ...(state.projectId ? { projectId: state.projectId } : {}),
163
+ ...(state.taskId ? { taskId: state.taskId } : {}),
164
+ ...(state.bindingEpisodeId
165
+ ? { bindingEpisodeId: state.bindingEpisodeId }
166
+ : {}),
167
+ ...(typeof state.version === "number" ? { version: state.version } : {}),
168
+ migration: {
169
+ operationId: outbox.operationId,
170
+ digest,
171
+ sourceCompanyUid: state.companyUid,
172
+ destinationCompanyUid: input.destinationCompanyUid,
173
+ destinationCompanySlug: input.destinationCompanySlug,
174
+ ...(Object.keys(destination).length ? { destination } : {}),
175
+ submittedAt: nowIso,
176
+ },
177
+ updatedAt: nowIso,
178
+ }, state);
179
+ try {
180
+ writeSessionState(pendingState, input.root);
181
+ }
182
+ catch (err) {
183
+ if (err instanceof NotTrackingError) {
184
+ return { ok: false, errorCode: "NOT_TRACKING", message: err.message };
185
+ }
186
+ throw err;
187
+ }
188
+ let receipt;
189
+ try {
190
+ receipt = await input.postMigrate({
191
+ sessionId: input.sessionId,
192
+ operationId: outbox.operationId,
193
+ digest,
194
+ sourceCompanyUid: state.companyUid,
195
+ destinationCompanyUid: input.destinationCompanyUid,
196
+ destination,
197
+ expectedVersion,
198
+ });
199
+ }
200
+ catch (err) {
201
+ const message = err instanceof Error ? err.message : String(err);
202
+ const retryable = /ECONN|ENOTFOUND|ETIMEDOUT|network|HTTP_5|429/i.test(message);
203
+ if (retryable) {
204
+ markOutboxQueued(outbox.operationId, input.root, "MIGRATE_RETRYABLE", nowFn);
205
+ }
206
+ else {
207
+ markOutboxQuarantined(outbox.operationId, input.root, "MIGRATE_FAILED", nowFn);
208
+ }
209
+ return {
210
+ ok: false,
211
+ pending: true,
212
+ outbox: outbox,
213
+ state: readSessionState(input.sessionId, input.root) ?? pendingState,
214
+ confirmationSummary,
215
+ errorCode: "MIGRATE_FAILED",
216
+ message,
217
+ };
218
+ }
219
+ if (!receipt?.receipt?.operationId ||
220
+ !receipt.destinationCompanyUid ||
221
+ !receipt.migratedAt) {
222
+ markOutboxQuarantined(outbox.operationId, input.root, "RECEIPT_INCOMPLETE", nowFn);
223
+ return {
224
+ ok: false,
225
+ pending: true,
226
+ outbox,
227
+ state: readSessionState(input.sessionId, input.root) ?? pendingState,
228
+ confirmationSummary,
229
+ errorCode: "RECEIPT_INCOMPLETE",
230
+ message: "Server response missing destination migrate receipt",
231
+ };
232
+ }
233
+ const acked = markOutboxAcked(outbox.operationId, input.root, receipt.receipt.operationId, nowFn) ?? outbox;
234
+ const appliedAt = nowFn().toISOString();
235
+ const destStatus = receipt.destination.projectId && receipt.destination.taskId
236
+ ? "bound"
237
+ : receipt.destination.projectId
238
+ ? "needs_task"
239
+ : "unresolved";
240
+ const applied = {
241
+ contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
242
+ sessionId: input.sessionId,
243
+ companyUid: receipt.destinationCompanyUid,
244
+ companySlug: input.destinationCompanySlug,
245
+ contextStatus: destStatus,
246
+ ...(receipt.destination.projectId
247
+ ? { projectId: receipt.destination.projectId }
248
+ : {}),
249
+ ...(receipt.destination.taskId
250
+ ? { taskId: receipt.destination.taskId }
251
+ : {}),
252
+ version: receipt.version,
253
+ migration: {
254
+ operationId: receipt.receipt.operationId,
255
+ digest: receipt.receipt.digest || digest,
256
+ sourceCompanyUid: receipt.sourceCompanyUid,
257
+ destinationCompanyUid: receipt.destinationCompanyUid,
258
+ destinationCompanySlug: input.destinationCompanySlug,
259
+ ...(Object.keys(receipt.destination).length
260
+ ? { destination: receipt.destination }
261
+ : {}),
262
+ submittedAt: nowIso,
263
+ receiptId: receipt.receipt.operationId,
264
+ completedAt: receipt.migratedAt,
265
+ },
266
+ updatedAt: appliedAt,
267
+ };
268
+ // Preserve local-only counters; drop source project/task when destination
269
+ // did not name replacements (migration never moves a project/task).
270
+ const merged = mergeLocalSessionFields(applied, {
271
+ ...state,
272
+ projectId: undefined,
273
+ taskId: undefined,
274
+ bindingEpisodeId: undefined,
275
+ decision: undefined,
276
+ });
277
+ writeSessionState(merged, input.root);
278
+ return {
279
+ ok: true,
280
+ pending: false,
281
+ receipt,
282
+ outbox: acked,
283
+ state: merged,
284
+ confirmationSummary,
285
+ };
286
+ }
287
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1,130 @@
1
+ /**
2
+ * hq mesh context organize — one-time decisions and US-005B submit (US-007B).
3
+ *
4
+ * CLI only presents server candidates and submits structured decisions.
5
+ * Ranking is server-owned. Success is reported only from the server receipt.
6
+ * Cancel / timeout / incapable surface leaves the session unresolved; the CLI
7
+ * never invents a project or adhoc target and never repeats the ask.
8
+ */
9
+ import { type SessionDecision, type SessionStateFile } from "./state.js";
10
+ export interface OrganizeCandidate {
11
+ candidateId: string;
12
+ projectId: string;
13
+ taskId?: string;
14
+ title: string;
15
+ projectName: string;
16
+ updatedAt?: string;
17
+ }
18
+ export interface CandidatesResponse {
19
+ contractVersion: 1;
20
+ sessionId: string;
21
+ companyUid: string;
22
+ decisionExpiresAt: string;
23
+ candidates: OrganizeCandidate[];
24
+ }
25
+ export type OrganizeDecisionKind = "bind_existing" | "create_project" | "create_task";
26
+ export interface OrganizeSubmitDecision {
27
+ kind: OrganizeDecisionKind;
28
+ projectId?: string;
29
+ taskId?: string;
30
+ title?: string;
31
+ }
32
+ export interface OrganizeReceipt {
33
+ sessionId: string;
34
+ companyUid: string;
35
+ projectId: string;
36
+ taskId?: string;
37
+ episodeId?: string;
38
+ bindingEpisodeId: string;
39
+ contextStatus: "bound";
40
+ version?: number;
41
+ openedAt?: string;
42
+ kind?: OrganizeDecisionKind;
43
+ receipt: {
44
+ operationId: string;
45
+ digest: string;
46
+ status: "committed" | "replayed";
47
+ };
48
+ }
49
+ export interface OrganizeListResult {
50
+ sessionId: string;
51
+ contextStatus: string;
52
+ pending: boolean;
53
+ decision: SessionDecision | null;
54
+ /** Why no ask is surfaced (threshold / already asked / bound / etc.). */
55
+ reason?: string;
56
+ }
57
+ export interface OrganizeSubmitResult {
58
+ ok: boolean;
59
+ receipt?: OrganizeReceipt;
60
+ localUntracked?: boolean;
61
+ errorCode?: string;
62
+ message?: string;
63
+ }
64
+ export type FetchCandidatesFn = (input: {
65
+ sessionId: string;
66
+ companyUid: string;
67
+ }) => Promise<CandidatesResponse>;
68
+ export type PostOrganizeFn = (input: {
69
+ sessionId: string;
70
+ companyUid: string;
71
+ operationId: string;
72
+ digest: string;
73
+ decision: OrganizeSubmitDecision;
74
+ expectedVersion: number;
75
+ decisionExpiresAt?: string;
76
+ }) => Promise<OrganizeReceipt>;
77
+ /** Stable decision id for a session + expiry window. */
78
+ export declare function stableDecisionId(sessionId: string, decisionExpiresAt: string): string;
79
+ export declare function digestOrganizePayload(parts: {
80
+ sessionId: string;
81
+ companyUid: string;
82
+ decision: OrganizeSubmitDecision;
83
+ expectedVersion: number;
84
+ }): string;
85
+ export declare function stableOrganizeOperationId(sessionId: string, decision: OrganizeSubmitDecision): string;
86
+ /**
87
+ * Build a pending decision from server candidates. Soft evidence ranks on the
88
+ * server; the CLI never auto-binds from this list.
89
+ */
90
+ export declare function decisionFromCandidates(sessionId: string, response: CandidatesResponse): SessionDecision;
91
+ /**
92
+ * Evaluate work-signal threshold and, when crossed for the first time, attach
93
+ * a one-time decision (optionally from server candidates). Never invents a
94
+ * project. If a decision already exists, it is left untouched (ask-once).
95
+ */
96
+ export declare function prepareOrganizeDecision(input: {
97
+ sessionId: string;
98
+ root: string;
99
+ now?: () => Date;
100
+ fetchCandidates?: FetchCandidatesFn;
101
+ /** Skip network; leave unresolved without candidates. */
102
+ offline?: boolean;
103
+ }): Promise<OrganizeListResult>;
104
+ export declare function formatOrganizeList(result: OrganizeListResult): string[];
105
+ /**
106
+ * Mark that the ask was cancelled / timed out / surface incapable.
107
+ * Leaves the session unresolved and visible; clears askAfter so the CLI
108
+ * never repeats the ask; never invents a project.
109
+ */
110
+ export declare function settleOrganizeAskWithoutBind(input: {
111
+ sessionId: string;
112
+ root: string;
113
+ now?: () => Date;
114
+ reason?: "cancelled" | "timeout" | "incapable";
115
+ }): SessionStateFile | null;
116
+ /**
117
+ * Submit an organize decision through US-005B. Success only from receipt.
118
+ */
119
+ export declare function submitOrganizeDecision(input: {
120
+ sessionId: string;
121
+ root: string;
122
+ decisionId?: string;
123
+ optionId?: string;
124
+ createProjectTitle?: string;
125
+ createTaskTitle?: string;
126
+ untracked?: boolean;
127
+ now?: () => Date;
128
+ postOrganize?: PostOrganizeFn;
129
+ }): Promise<OrganizeSubmitResult>;
130
+ //# sourceMappingURL=organize.d.ts.map