open-multi-agent-kit 0.80.4 → 0.80.6

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 (105) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/config.d.ts +4 -9
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +46 -15
  5. package/dist/config.js.map +1 -1
  6. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  7. package/dist/core/compaction/branch-summarization.js +6 -1
  8. package/dist/core/compaction/branch-summarization.js.map +1 -1
  9. package/dist/core/compaction/compaction.d.ts +82 -1
  10. package/dist/core/compaction/compaction.d.ts.map +1 -1
  11. package/dist/core/compaction/compaction.js +606 -10
  12. package/dist/core/compaction/compaction.js.map +1 -1
  13. package/dist/core/harness-control-events.d.ts +79 -0
  14. package/dist/core/harness-control-events.d.ts.map +1 -0
  15. package/dist/core/harness-control-events.js +339 -0
  16. package/dist/core/harness-control-events.js.map +1 -0
  17. package/dist/core/harness-control-replay.d.ts +26 -0
  18. package/dist/core/harness-control-replay.d.ts.map +1 -0
  19. package/dist/core/harness-control-replay.js +155 -0
  20. package/dist/core/harness-control-replay.js.map +1 -0
  21. package/dist/core/keybindings.d.ts +12 -0
  22. package/dist/core/keybindings.d.ts.map +1 -1
  23. package/dist/core/keybindings.js +31 -1
  24. package/dist/core/keybindings.js.map +1 -1
  25. package/dist/core/mcp-inventory.d.ts +40 -0
  26. package/dist/core/mcp-inventory.d.ts.map +1 -0
  27. package/dist/core/mcp-inventory.js +104 -0
  28. package/dist/core/mcp-inventory.js.map +1 -0
  29. package/dist/core/settings-manager.d.ts +3 -0
  30. package/dist/core/settings-manager.d.ts.map +1 -1
  31. package/dist/core/settings-manager.js +4 -0
  32. package/dist/core/settings-manager.js.map +1 -1
  33. package/dist/core/slash-commands.d.ts.map +1 -1
  34. package/dist/core/slash-commands.js +4 -1
  35. package/dist/core/slash-commands.js.map +1 -1
  36. package/dist/core/spec-kit/compiler.d.ts +67 -0
  37. package/dist/core/spec-kit/compiler.d.ts.map +1 -0
  38. package/dist/core/spec-kit/compiler.js +268 -0
  39. package/dist/core/spec-kit/compiler.js.map +1 -0
  40. package/dist/core/transaction-coordinator.d.ts +30 -0
  41. package/dist/core/transaction-coordinator.d.ts.map +1 -0
  42. package/dist/core/transaction-coordinator.js +173 -0
  43. package/dist/core/transaction-coordinator.js.map +1 -0
  44. package/dist/migrations.d.ts +35 -0
  45. package/dist/migrations.d.ts.map +1 -1
  46. package/dist/migrations.js +409 -3
  47. package/dist/migrations.js.map +1 -1
  48. package/dist/modes/interactive/components/control-panel.d.ts +29 -0
  49. package/dist/modes/interactive/components/control-panel.d.ts.map +1 -0
  50. package/dist/modes/interactive/components/control-panel.js +90 -0
  51. package/dist/modes/interactive/components/control-panel.js.map +1 -0
  52. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  53. package/dist/modes/interactive/components/custom-editor.js +21 -13
  54. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  55. package/dist/modes/interactive/components/index.d.ts +3 -0
  56. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  57. package/dist/modes/interactive/components/index.js +3 -0
  58. package/dist/modes/interactive/components/index.js.map +1 -1
  59. package/dist/modes/interactive/components/mcp-selector.d.ts +28 -0
  60. package/dist/modes/interactive/components/mcp-selector.d.ts.map +1 -0
  61. package/dist/modes/interactive/components/mcp-selector.js +138 -0
  62. package/dist/modes/interactive/components/mcp-selector.js.map +1 -0
  63. package/dist/modes/interactive/components/model-selector.d.ts +12 -3
  64. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  65. package/dist/modes/interactive/components/model-selector.js +120 -27
  66. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  67. package/dist/modes/interactive/components/settings-selector.d.ts +19 -1
  68. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/settings-selector.js +21 -8
  70. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  71. package/dist/modes/interactive/components/skills-selector.d.ts +32 -0
  72. package/dist/modes/interactive/components/skills-selector.d.ts.map +1 -0
  73. package/dist/modes/interactive/components/skills-selector.js +130 -0
  74. package/dist/modes/interactive/components/skills-selector.js.map +1 -0
  75. package/dist/modes/interactive/components/thinking-selector.d.ts +7 -8
  76. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/thinking-selector.js +7 -48
  78. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  79. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  80. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  81. package/dist/modes/interactive/interactive-mode.js +340 -97
  82. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  83. package/dist/modes/interactive/theme/catppuccin-mocha.json +86 -0
  84. package/dist/modes/interactive/theme/dracula.json +86 -0
  85. package/dist/modes/interactive/theme/gruvbox-dark.json +86 -0
  86. package/dist/modes/interactive/theme/kanagawa-dragon.json +86 -0
  87. package/dist/modes/interactive/theme/omk-control-panel.json +91 -0
  88. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  89. package/dist/modes/interactive/theme/theme.js +41 -16
  90. package/dist/modes/interactive/theme/theme.js.map +1 -1
  91. package/dist/modes/interactive/theme/tokyo-night-storm.json +86 -0
  92. package/docs/compaction.md +21 -8
  93. package/docs/quickstart.md +1 -1
  94. package/docs/themes.md +10 -2
  95. package/docs/usage.md +2 -1
  96. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  97. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  98. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  99. package/examples/extensions/gondolin/package-lock.json +2 -2
  100. package/examples/extensions/gondolin/package.json +1 -1
  101. package/examples/extensions/sandbox/package-lock.json +2 -2
  102. package/examples/extensions/sandbox/package.json +1 -1
  103. package/examples/extensions/with-deps/package-lock.json +2 -2
  104. package/examples/extensions/with-deps/package.json +1 -1
  105. package/package.json +4 -4
@@ -0,0 +1,173 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { recordHarnessControlEvent, } from "./harness-control-events.js";
3
+ function errorSummary(error) {
4
+ if (error instanceof Error)
5
+ return { name: error.name, message: error.message };
6
+ return { name: "Error", message: String(error) };
7
+ }
8
+ function isThenable(value) {
9
+ return ((typeof value === "object" || typeof value === "function") &&
10
+ value !== null &&
11
+ typeof value.then === "function");
12
+ }
13
+ function createEventOptions(base, operationId, causationId) {
14
+ return {
15
+ ...base,
16
+ operationId,
17
+ correlationId: base?.correlationId ?? operationId,
18
+ causationId: causationId ?? base?.causationId ?? null,
19
+ };
20
+ }
21
+ function pushTransactionEvent(events, kind, status, data, eventOptions, operationId, causationId, beforeState, afterState) {
22
+ const written = recordHarnessControlEvent(kind, status, data, {
23
+ ...createEventOptions(eventOptions, operationId, causationId),
24
+ beforeState,
25
+ afterState,
26
+ });
27
+ events.push(written);
28
+ return written.event?.eventId ?? causationId;
29
+ }
30
+ function createRollbackContext(phase, prepared, value) {
31
+ return { phase, prepared, value };
32
+ }
33
+ export function runHarnessControlTransactionSync(options) {
34
+ const operationId = options.eventOptions?.operationId ?? randomUUID();
35
+ const data = options.data ?? {};
36
+ const events = [];
37
+ let causationId = null;
38
+ let phase = "prepare";
39
+ let prepared;
40
+ let value;
41
+ try {
42
+ const preparedValue = options.prepare ? options.prepare() : undefined;
43
+ if (isThenable(preparedValue)) {
44
+ throw new Error("Synchronous harness transaction prepare returned a Promise");
45
+ }
46
+ prepared = preparedValue;
47
+ causationId = pushTransactionEvent(events, options.kind, "prepared", { ...data, phase: "prepare" }, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
48
+ causationId = pushTransactionEvent(events, options.kind, "started", data, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
49
+ phase = "apply";
50
+ causationId = pushTransactionEvent(events, options.kind, "applying", { ...data, phase: "apply" }, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
51
+ const committed = options.commit(prepared);
52
+ if (isThenable(committed)) {
53
+ throw new Error("Synchronous harness transaction commit returned a Promise");
54
+ }
55
+ value = committed;
56
+ phase = "verify";
57
+ causationId = pushTransactionEvent(events, options.kind, "verifying", { ...data, phase: "verify" }, options.eventOptions, operationId, causationId, options.beforeState, options.afterState ? options.afterState(value) : value);
58
+ const verified = options.verify ? options.verify(value, prepared) : undefined;
59
+ if (isThenable(verified)) {
60
+ throw new Error("Synchronous harness transaction verify returned a Promise");
61
+ }
62
+ events.push(recordHarnessControlEvent(options.kind, "completed", data, {
63
+ ...createEventOptions(options.eventOptions, operationId, causationId),
64
+ beforeState: options.beforeState,
65
+ afterState: options.afterState ? options.afterState(value) : value,
66
+ }));
67
+ return { status: "completed", operationId, value, events };
68
+ }
69
+ catch (error) {
70
+ return handleSyncTransactionFailure(options, {
71
+ error,
72
+ events,
73
+ operationId,
74
+ causationId,
75
+ phase,
76
+ prepared,
77
+ value,
78
+ data,
79
+ });
80
+ }
81
+ }
82
+ function handleSyncTransactionFailure(options, context) {
83
+ const { error, events, operationId, causationId, phase, prepared, value, data } = context;
84
+ if (!options.rollback) {
85
+ events.push(recordHarnessControlEvent(options.kind, "failed", { ...data, phase, error: errorSummary(error) }, {
86
+ ...createEventOptions(options.eventOptions, operationId, causationId),
87
+ beforeState: options.beforeState,
88
+ }));
89
+ return { status: "failed", operationId, error, events };
90
+ }
91
+ try {
92
+ options.rollback(error, createRollbackContext(phase, prepared, value));
93
+ events.push(recordHarnessControlEvent(options.kind, "rolled_back", { ...data, phase, error: errorSummary(error) }, {
94
+ ...createEventOptions(options.eventOptions, operationId, causationId),
95
+ beforeState: options.beforeState,
96
+ afterState: options.beforeState,
97
+ }));
98
+ return { status: "rolled_back", operationId, error, events };
99
+ }
100
+ catch (rollbackError) {
101
+ events.push(recordHarnessControlEvent(options.kind, "in_doubt", { ...data, phase, error: errorSummary(error), rollbackError: errorSummary(rollbackError) }, {
102
+ ...createEventOptions(options.eventOptions, operationId, causationId),
103
+ beforeState: options.beforeState,
104
+ }));
105
+ return { status: "in_doubt", operationId, error, rollbackError, events };
106
+ }
107
+ }
108
+ export async function runHarnessControlTransaction(options) {
109
+ const operationId = options.eventOptions?.operationId ?? randomUUID();
110
+ const data = options.data ?? {};
111
+ const events = [];
112
+ let causationId = null;
113
+ let phase = "prepare";
114
+ let prepared;
115
+ let value;
116
+ try {
117
+ prepared = options.prepare ? await options.prepare() : undefined;
118
+ causationId = pushTransactionEvent(events, options.kind, "prepared", { ...data, phase: "prepare" }, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
119
+ causationId = pushTransactionEvent(events, options.kind, "started", data, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
120
+ phase = "apply";
121
+ causationId = pushTransactionEvent(events, options.kind, "applying", { ...data, phase: "apply" }, options.eventOptions, operationId, causationId, options.beforeState, prepared ?? options.beforeState);
122
+ value = await options.commit(prepared);
123
+ phase = "verify";
124
+ causationId = pushTransactionEvent(events, options.kind, "verifying", { ...data, phase: "verify" }, options.eventOptions, operationId, causationId, options.beforeState, options.afterState ? options.afterState(value) : value);
125
+ if (options.verify)
126
+ await options.verify(value, prepared);
127
+ events.push(recordHarnessControlEvent(options.kind, "completed", data, {
128
+ ...createEventOptions(options.eventOptions, operationId, causationId),
129
+ beforeState: options.beforeState,
130
+ afterState: options.afterState ? options.afterState(value) : value,
131
+ }));
132
+ return { status: "completed", operationId, value, events };
133
+ }
134
+ catch (error) {
135
+ return handleAsyncTransactionFailure(options, {
136
+ error,
137
+ events,
138
+ operationId,
139
+ causationId,
140
+ phase,
141
+ prepared,
142
+ value,
143
+ data,
144
+ });
145
+ }
146
+ }
147
+ async function handleAsyncTransactionFailure(options, context) {
148
+ const { error, events, operationId, causationId, phase, prepared, value, data } = context;
149
+ if (!options.rollback) {
150
+ events.push(recordHarnessControlEvent(options.kind, "failed", { ...data, phase, error: errorSummary(error) }, {
151
+ ...createEventOptions(options.eventOptions, operationId, causationId),
152
+ beforeState: options.beforeState,
153
+ }));
154
+ return { status: "failed", operationId, error, events };
155
+ }
156
+ try {
157
+ await options.rollback(error, createRollbackContext(phase, prepared, value));
158
+ events.push(recordHarnessControlEvent(options.kind, "rolled_back", { ...data, phase, error: errorSummary(error) }, {
159
+ ...createEventOptions(options.eventOptions, operationId, causationId),
160
+ beforeState: options.beforeState,
161
+ afterState: options.beforeState,
162
+ }));
163
+ return { status: "rolled_back", operationId, error, events };
164
+ }
165
+ catch (rollbackError) {
166
+ events.push(recordHarnessControlEvent(options.kind, "in_doubt", { ...data, phase, error: errorSummary(error), rollbackError: errorSummary(rollbackError) }, {
167
+ ...createEventOptions(options.eventOptions, operationId, causationId),
168
+ beforeState: options.beforeState,
169
+ }));
170
+ return { status: "in_doubt", operationId, error, rollbackError, events };
171
+ }
172
+ }
173
+ //# sourceMappingURL=transaction-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-coordinator.js","sourceRoot":"","sources":["../../src/core/transaction-coordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n\ttype HarnessControlEventKind,\n\ttype HarnessControlEventOptions,\n\ttype HarnessControlEventStatus,\n\ttype HarnessControlEventWriteResult,\n\trecordHarnessControlEvent,\n} from \"./harness-control-events.ts\";\n\nexport type HarnessControlTransactionStatus = \"completed\" | \"rolled_back\" | \"failed\" | \"in_doubt\";\nexport type HarnessControlTransactionPhase = \"prepare\" | \"apply\" | \"verify\";\n\nexport interface HarnessControlRollbackContext<T, Prepared = unknown> {\n\tphase: HarnessControlTransactionPhase;\n\tprepared?: Prepared;\n\tvalue?: T;\n}\n\nexport interface HarnessControlTransactionOptions<T, Prepared = unknown> {\n\tkind: HarnessControlEventKind;\n\tdata?: Record<string, unknown>;\n\tbeforeState?: unknown;\n\tafterState?: (value: T) => unknown;\n\tprepare?: () => Prepared | Promise<Prepared>;\n\tcommit: (prepared?: Prepared) => T | Promise<T>;\n\tverify?: (value: T, prepared?: Prepared) => void | Promise<void>;\n\trollback?: (error: unknown, context?: HarnessControlRollbackContext<T, Prepared>) => void | Promise<void>;\n\teventOptions?: HarnessControlEventOptions;\n}\n\nexport interface HarnessControlTransactionResult<T> {\n\tstatus: HarnessControlTransactionStatus;\n\toperationId: string;\n\tvalue?: T;\n\terror?: unknown;\n\trollbackError?: unknown;\n\tevents: HarnessControlEventWriteResult[];\n}\n\nfunction errorSummary(error: unknown): Record<string, string> {\n\tif (error instanceof Error) return { name: error.name, message: error.message };\n\treturn { name: \"Error\", message: String(error) };\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n\treturn (\n\t\t(typeof value === \"object\" || typeof value === \"function\") &&\n\t\tvalue !== null &&\n\t\ttypeof (value as { then?: unknown }).then === \"function\"\n\t);\n}\n\nfunction createEventOptions(\n\tbase: HarnessControlEventOptions | undefined,\n\toperationId: string,\n\tcausationId?: string | null,\n): HarnessControlEventOptions {\n\treturn {\n\t\t...base,\n\t\toperationId,\n\t\tcorrelationId: base?.correlationId ?? operationId,\n\t\tcausationId: causationId ?? base?.causationId ?? null,\n\t};\n}\n\nfunction pushTransactionEvent(\n\tevents: HarnessControlEventWriteResult[],\n\tkind: HarnessControlEventKind,\n\tstatus: HarnessControlEventStatus,\n\tdata: Record<string, unknown>,\n\teventOptions: HarnessControlEventOptions | undefined,\n\toperationId: string,\n\tcausationId: string | null,\n\tbeforeState: unknown,\n\tafterState?: unknown,\n): string | null {\n\tconst written = recordHarnessControlEvent(kind, status, data, {\n\t\t...createEventOptions(eventOptions, operationId, causationId),\n\t\tbeforeState,\n\t\tafterState,\n\t});\n\tevents.push(written);\n\treturn written.event?.eventId ?? causationId;\n}\n\nfunction createRollbackContext<T, Prepared>(\n\tphase: HarnessControlTransactionPhase,\n\tprepared: Prepared | undefined,\n\tvalue: T | undefined,\n): HarnessControlRollbackContext<T, Prepared> {\n\treturn { phase, prepared, value };\n}\n\nexport function runHarnessControlTransactionSync<T, Prepared = unknown>(\n\toptions: HarnessControlTransactionOptions<T, Prepared>,\n): HarnessControlTransactionResult<T> {\n\tconst operationId = options.eventOptions?.operationId ?? randomUUID();\n\tconst data = options.data ?? {};\n\tconst events: HarnessControlEventWriteResult[] = [];\n\tlet causationId: string | null = null;\n\tlet phase: HarnessControlTransactionPhase = \"prepare\";\n\tlet prepared: Prepared | undefined;\n\tlet value: T | undefined;\n\n\ttry {\n\t\tconst preparedValue = options.prepare ? options.prepare() : undefined;\n\t\tif (isThenable(preparedValue)) {\n\t\t\tthrow new Error(\"Synchronous harness transaction prepare returned a Promise\");\n\t\t}\n\t\tprepared = preparedValue as Prepared | undefined;\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"prepared\",\n\t\t\t{ ...data, phase: \"prepare\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"started\",\n\t\t\tdata,\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\n\t\tphase = \"apply\";\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"applying\",\n\t\t\t{ ...data, phase: \"apply\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\t\tconst committed = options.commit(prepared);\n\t\tif (isThenable(committed)) {\n\t\t\tthrow new Error(\"Synchronous harness transaction commit returned a Promise\");\n\t\t}\n\t\tvalue = committed;\n\n\t\tphase = \"verify\";\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"verifying\",\n\t\t\t{ ...data, phase: \"verify\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\toptions.afterState ? options.afterState(value) : value,\n\t\t);\n\t\tconst verified = options.verify ? options.verify(value, prepared) : undefined;\n\t\tif (isThenable(verified)) {\n\t\t\tthrow new Error(\"Synchronous harness transaction verify returned a Promise\");\n\t\t}\n\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(options.kind, \"completed\", data, {\n\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\tafterState: options.afterState ? options.afterState(value) : value,\n\t\t\t}),\n\t\t);\n\t\treturn { status: \"completed\", operationId, value, events };\n\t} catch (error) {\n\t\treturn handleSyncTransactionFailure(options, {\n\t\t\terror,\n\t\t\tevents,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\tphase,\n\t\t\tprepared,\n\t\t\tvalue,\n\t\t\tdata,\n\t\t});\n\t}\n}\n\nfunction handleSyncTransactionFailure<T, Prepared>(\n\toptions: HarnessControlTransactionOptions<T, Prepared>,\n\tcontext: {\n\t\terror: unknown;\n\t\tevents: HarnessControlEventWriteResult[];\n\t\toperationId: string;\n\t\tcausationId: string | null;\n\t\tphase: HarnessControlTransactionPhase;\n\t\tprepared: Prepared | undefined;\n\t\tvalue: T | undefined;\n\t\tdata: Record<string, unknown>;\n\t},\n): HarnessControlTransactionResult<T> {\n\tconst { error, events, operationId, causationId, phase, prepared, value, data } = context;\n\tif (!options.rollback) {\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"failed\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"failed\", operationId, error, events };\n\t}\n\n\ttry {\n\t\toptions.rollback(error, createRollbackContext(phase, prepared, value));\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"rolled_back\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t\tafterState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"rolled_back\", operationId, error, events };\n\t} catch (rollbackError) {\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"in_doubt\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error), rollbackError: errorSummary(rollbackError) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"in_doubt\", operationId, error, rollbackError, events };\n\t}\n}\n\nexport async function runHarnessControlTransaction<T, Prepared = unknown>(\n\toptions: HarnessControlTransactionOptions<T, Prepared>,\n): Promise<HarnessControlTransactionResult<T>> {\n\tconst operationId = options.eventOptions?.operationId ?? randomUUID();\n\tconst data = options.data ?? {};\n\tconst events: HarnessControlEventWriteResult[] = [];\n\tlet causationId: string | null = null;\n\tlet phase: HarnessControlTransactionPhase = \"prepare\";\n\tlet prepared: Prepared | undefined;\n\tlet value: T | undefined;\n\n\ttry {\n\t\tprepared = options.prepare ? await options.prepare() : undefined;\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"prepared\",\n\t\t\t{ ...data, phase: \"prepare\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"started\",\n\t\t\tdata,\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\n\t\tphase = \"apply\";\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"applying\",\n\t\t\t{ ...data, phase: \"apply\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\tprepared ?? options.beforeState,\n\t\t);\n\t\tvalue = await options.commit(prepared);\n\n\t\tphase = \"verify\";\n\t\tcausationId = pushTransactionEvent(\n\t\t\tevents,\n\t\t\toptions.kind,\n\t\t\t\"verifying\",\n\t\t\t{ ...data, phase: \"verify\" },\n\t\t\toptions.eventOptions,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\toptions.beforeState,\n\t\t\toptions.afterState ? options.afterState(value) : value,\n\t\t);\n\t\tif (options.verify) await options.verify(value, prepared);\n\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(options.kind, \"completed\", data, {\n\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\tafterState: options.afterState ? options.afterState(value) : value,\n\t\t\t}),\n\t\t);\n\t\treturn { status: \"completed\", operationId, value, events };\n\t} catch (error) {\n\t\treturn handleAsyncTransactionFailure(options, {\n\t\t\terror,\n\t\t\tevents,\n\t\t\toperationId,\n\t\t\tcausationId,\n\t\t\tphase,\n\t\t\tprepared,\n\t\t\tvalue,\n\t\t\tdata,\n\t\t});\n\t}\n}\n\nasync function handleAsyncTransactionFailure<T, Prepared>(\n\toptions: HarnessControlTransactionOptions<T, Prepared>,\n\tcontext: {\n\t\terror: unknown;\n\t\tevents: HarnessControlEventWriteResult[];\n\t\toperationId: string;\n\t\tcausationId: string | null;\n\t\tphase: HarnessControlTransactionPhase;\n\t\tprepared: Prepared | undefined;\n\t\tvalue: T | undefined;\n\t\tdata: Record<string, unknown>;\n\t},\n): Promise<HarnessControlTransactionResult<T>> {\n\tconst { error, events, operationId, causationId, phase, prepared, value, data } = context;\n\tif (!options.rollback) {\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"failed\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"failed\", operationId, error, events };\n\t}\n\n\ttry {\n\t\tawait options.rollback(error, createRollbackContext(phase, prepared, value));\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"rolled_back\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t\tafterState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"rolled_back\", operationId, error, events };\n\t} catch (rollbackError) {\n\t\tevents.push(\n\t\t\trecordHarnessControlEvent(\n\t\t\t\toptions.kind,\n\t\t\t\t\"in_doubt\",\n\t\t\t\t{ ...data, phase, error: errorSummary(error), rollbackError: errorSummary(rollbackError) },\n\t\t\t\t{\n\t\t\t\t\t...createEventOptions(options.eventOptions, operationId, causationId),\n\t\t\t\t\tbeforeState: options.beforeState,\n\t\t\t\t},\n\t\t\t),\n\t\t);\n\t\treturn { status: \"in_doubt\", operationId, error, rollbackError, events };\n\t}\n}\n"]}
@@ -17,6 +17,41 @@ export declare function migrateAuthToAuthJson(): string[];
17
17
  * See: https://github.com/dmae97/open-multi-agent-kit/issues/320
18
18
  */
19
19
  export declare function migrateSessionsFromAgentRoot(): void;
20
+ export type LegacyExtensionClassification = "legacy" | "not-legacy" | "unknown";
21
+ export interface ExtensionDeprecationDiagnostic {
22
+ code: "LEGACY_EXTENSION_ENTRYPOINT" | "LEGACY_EXTENSION_UNKNOWN";
23
+ scope: "global" | "project";
24
+ path: string;
25
+ classification: LegacyExtensionClassification;
26
+ confidence: number;
27
+ evidence: string[];
28
+ recommendedAction: "move-to-extensions" | "inspect-manifest";
29
+ }
30
+ export interface ExtensionMigrationAction {
31
+ action: "move";
32
+ scope: "global" | "project";
33
+ from: string;
34
+ to: string;
35
+ status: "ready" | "blocked" | "applied" | "rolled_back" | "in_doubt";
36
+ reason: string;
37
+ blocker?: string;
38
+ sourceStatHash?: string;
39
+ sourceContentHash?: string;
40
+ recoveryJournalPath?: string;
41
+ sourceStat?: {
42
+ isDirectory: boolean;
43
+ size: number;
44
+ mtimeMs: number;
45
+ mode: number;
46
+ };
47
+ }
48
+ export interface ExtensionMigrationPlan {
49
+ diagnostics: ExtensionDeprecationDiagnostic[];
50
+ actions: ExtensionMigrationAction[];
51
+ }
52
+ export declare function getExtensionDeprecationDiagnostics(cwd: string): ExtensionDeprecationDiagnostic[];
53
+ export declare function createExtensionMigrationPlan(cwd: string): ExtensionMigrationPlan;
54
+ export declare function applyExtensionMigrationPlan(cwd: string, plan?: ExtensionMigrationPlan): ExtensionMigrationPlan;
20
55
  /**
21
56
  * Print deprecation warnings and wait for keypress.
22
57
  */
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAoDhD;AAmID;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CA+CnD;AA+ID;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAQA","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport chalk from \"chalk\";\nimport { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.ts\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.ts\";\nimport { isLegacyEnvVarNameConfigValue } from \"./core/resolve-config-value.ts\";\nimport { stripJsonComments } from \"./utils/json.ts\";\n\nconst MIGRATION_GUIDE_URL =\n\t\"https://github.com/dmae97/open-multi-agent-kit/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration\";\nconst EXTENSIONS_DOC_URL =\n\t\"https://github.com/dmae97/open-multi-agent-kit/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\ninterface ConfigValueMigration {\n\tlocation: string;\n\tfrom: string;\n\tto: string;\n}\n\nfunction migrateLegacyEnvVarString(value: string): string | undefined {\n\treturn isLegacyEnvVarNameConfigValue(value) ? `$${value}` : undefined;\n}\n\nfunction migrateStringProperty(\n\trecord: Record<string, unknown>,\n\tkey: string,\n\tlocation: string,\n\tmigrations: ConfigValueMigration[],\n): boolean {\n\tconst value = record[key];\n\tif (typeof value !== \"string\") return false;\n\tconst migrated = migrateLegacyEnvVarString(value);\n\tif (migrated === undefined) return false;\n\trecord[key] = migrated;\n\tmigrations.push({ location, from: value, to: migrated });\n\treturn true;\n}\n\nfunction migrateHeadersConfig(headers: unknown, location: string, migrations: ConfigValueMigration[]): boolean {\n\tif (typeof headers !== \"object\" || headers === null || Array.isArray(headers)) return false;\n\tconst headerRecord = headers as Record<string, unknown>;\n\tlet migrated = false;\n\tfor (const [key, value] of Object.entries(headerRecord)) {\n\t\tif (typeof value !== \"string\") continue;\n\t\tconst migratedValue = migrateLegacyEnvVarString(value);\n\t\tif (migratedValue === undefined) continue;\n\t\theaderRecord[key] = migratedValue;\n\t\tmigrations.push({ location: `${location}[${JSON.stringify(key)}]`, from: value, to: migratedValue });\n\t\tmigrated = true;\n\t}\n\treturn migrated;\n}\n\nfunction migrateAuthJsonConfigValues(agentDir: string): ConfigValueMigration[] {\n\tconst authPath = join(agentDir, \"auth.json\");\n\tif (!existsSync(authPath)) return [];\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(authPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return [];\n\t\tconst authData = parsed as Record<string, unknown>;\n\n\t\tconst migrations: ConfigValueMigration[] = [];\n\t\tfor (const [provider, credential] of Object.entries(authData)) {\n\t\t\tif (typeof credential !== \"object\" || credential === null || Array.isArray(credential)) continue;\n\t\t\tconst credentialRecord = credential as Record<string, unknown>;\n\t\t\tif (credentialRecord.type !== \"api_key\") continue;\n\t\t\tmigrateStringProperty(credentialRecord, \"key\", `auth.json[${JSON.stringify(provider)}].key`, migrations);\n\t\t}\n\n\t\tif (migrations.length === 0) return [];\n\t\twriteFileSync(authPath, `${JSON.stringify(parsed, null, 2)}\\n`, \"utf-8\");\n\t\tchmodSync(authPath, 0o600);\n\t\treturn migrations;\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[] {\n\tconst modelsPath = join(agentDir, \"models.json\");\n\tif (!existsSync(modelsPath)) return [];\n\n\tconst parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, \"utf-8\"))) as unknown;\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return [];\n\tconst modelsData = parsed as Record<string, unknown>;\n\tconst providers = modelsData.providers;\n\tif (typeof providers !== \"object\" || providers === null || Array.isArray(providers)) return [];\n\n\tconst migrations: ConfigValueMigration[] = [];\n\tfor (const [provider, providerConfig] of Object.entries(providers)) {\n\t\tif (typeof providerConfig !== \"object\" || providerConfig === null || Array.isArray(providerConfig)) continue;\n\t\tconst providerRecord = providerConfig as Record<string, unknown>;\n\t\tconst providerLocation = `models.json.providers[${JSON.stringify(provider)}]`;\n\t\tmigrateStringProperty(providerRecord, \"apiKey\", `${providerLocation}.apiKey`, migrations);\n\t\tmigrateHeadersConfig(providerRecord.headers, `${providerLocation}.headers`, migrations);\n\n\t\tif (Array.isArray(providerRecord.models)) {\n\t\t\tfor (let index = 0; index < providerRecord.models.length; index++) {\n\t\t\t\tconst modelConfig = providerRecord.models[index];\n\t\t\t\tif (typeof modelConfig !== \"object\" || modelConfig === null || Array.isArray(modelConfig)) continue;\n\t\t\t\tconst modelRecord = modelConfig as Record<string, unknown>;\n\t\t\t\tconst modelKey = typeof modelRecord.id === \"string\" ? JSON.stringify(modelRecord.id) : String(index);\n\t\t\t\tmigrateHeadersConfig(modelRecord.headers, `${providerLocation}.models[${modelKey}].headers`, migrations);\n\t\t\t}\n\t\t}\n\n\t\tconst modelOverrides = providerRecord.modelOverrides;\n\t\tif (typeof modelOverrides === \"object\" && modelOverrides !== null && !Array.isArray(modelOverrides)) {\n\t\t\tfor (const [modelId, modelOverride] of Object.entries(modelOverrides)) {\n\t\t\t\tif (typeof modelOverride !== \"object\" || modelOverride === null || Array.isArray(modelOverride)) continue;\n\t\t\t\tconst modelOverrideRecord = modelOverride as Record<string, unknown>;\n\t\t\t\tmigrateHeadersConfig(\n\t\t\t\t\tmodelOverrideRecord.headers,\n\t\t\t\t\t`${providerLocation}.modelOverrides[${JSON.stringify(modelId)}].headers`,\n\t\t\t\t\tmigrations,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (migrations.length === 0) return [];\n\twriteFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\\n`, \"utf-8\");\n\treturn migrations;\n}\n\nfunction migrateExplicitEnvVarConfigValues(): void {\n\tconst agentDir = getAgentDir();\n\tconst migrations = [...migrateAuthJsonConfigValues(agentDir), ...migrateModelsJsonConfigValues(agentDir)];\n\tif (migrations.length === 0) return;\n\n\tconst details = migrations.map((migration) => ` - ${migration.location}: ${migration.from} -> ${migration.to}`);\n\tconsole.log(\n\t\tchalk.yellow(\n\t\t\t[\n\t\t\t\t\"Warning: Migrated API key/header environment references to explicit $ENV_VAR syntax. Plain strings will be treated as literals.\",\n\t\t\t\t...details,\n\t\t\t].join(\"\\n\"),\n\t\t),\n\t);\n}\n\n/**\n * Migrate sessions from ~/.omk/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.omk/agent/ instead of\n * ~/.omk/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/dmae97/open-multi-agent-kit/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file\n\t\t\tconst fileName = file.split(\"/\").pop() || file.split(\"\\\\\").pop();\n\t\t\tconst newPath = join(correctDir, fileName!);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\n\tif (existsSync(hooksDir)) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\tawait new Promise<void>((resolve) => {\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", () => {\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tresolve();\n\t\t});\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateExplicitEnvVarConfigValues();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AA6BH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAoDhD;AAmID;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CA+CnD;AA+FD,MAAM,MAAM,6BAA6B,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,CAAC;AAEhF,MAAM,WAAW,8BAA8B;IAC9C,IAAI,EAAE,6BAA6B,GAAG,0BAA0B,CAAC;IACjE,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,6BAA6B,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,iBAAiB,EAAE,oBAAoB,GAAG,kBAAkB,CAAC;CAC7D;AAED,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,CAAC;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE;QACZ,WAAW,EAAE,OAAO,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACb,CAAC;CACF;AAED,MAAM,WAAW,sBAAsB;IACtC,WAAW,EAAE,8BAA8B,EAAE,CAAC;IAC9C,OAAO,EAAE,wBAAwB,EAAE,CAAC;CACpC;AAiPD,wBAAgB,kCAAkC,CAAC,GAAG,EAAE,MAAM,GAAG,8BAA8B,EAAE,CAIhG;AAiFD,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,CA6ChF;AAED,wBAAgB,2BAA2B,CAC1C,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,sBAA0D,GAC9D,sBAAsB,CAiGxB;AA2DD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAQA","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport { createHash } from \"node:crypto\";\nimport chalk from \"chalk\";\nimport {\n\tchmodSync,\n\texistsSync,\n\tlstatSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trealpathSync,\n\trenameSync,\n\trmSync,\n\twriteFileSync,\n} from \"fs\";\nimport { basename, dirname, join, relative, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.ts\";\nimport { recordHarnessControlEvent } from \"./core/harness-control-events.ts\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.ts\";\nimport { isLegacyEnvVarNameConfigValue } from \"./core/resolve-config-value.ts\";\nimport { runHarnessControlTransactionSync } from \"./core/transaction-coordinator.ts\";\nimport { stripJsonComments } from \"./utils/json.ts\";\n\nconst MIGRATION_GUIDE_URL =\n\t\"https://github.com/dmae97/open-multi-agent-kit/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration\";\nconst EXTENSIONS_DOC_URL =\n\t\"https://github.com/dmae97/open-multi-agent-kit/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\ninterface ConfigValueMigration {\n\tlocation: string;\n\tfrom: string;\n\tto: string;\n}\n\nfunction migrateLegacyEnvVarString(value: string): string | undefined {\n\treturn isLegacyEnvVarNameConfigValue(value) ? `$${value}` : undefined;\n}\n\nfunction migrateStringProperty(\n\trecord: Record<string, unknown>,\n\tkey: string,\n\tlocation: string,\n\tmigrations: ConfigValueMigration[],\n): boolean {\n\tconst value = record[key];\n\tif (typeof value !== \"string\") return false;\n\tconst migrated = migrateLegacyEnvVarString(value);\n\tif (migrated === undefined) return false;\n\trecord[key] = migrated;\n\tmigrations.push({ location, from: value, to: migrated });\n\treturn true;\n}\n\nfunction migrateHeadersConfig(headers: unknown, location: string, migrations: ConfigValueMigration[]): boolean {\n\tif (typeof headers !== \"object\" || headers === null || Array.isArray(headers)) return false;\n\tconst headerRecord = headers as Record<string, unknown>;\n\tlet migrated = false;\n\tfor (const [key, value] of Object.entries(headerRecord)) {\n\t\tif (typeof value !== \"string\") continue;\n\t\tconst migratedValue = migrateLegacyEnvVarString(value);\n\t\tif (migratedValue === undefined) continue;\n\t\theaderRecord[key] = migratedValue;\n\t\tmigrations.push({ location: `${location}[${JSON.stringify(key)}]`, from: value, to: migratedValue });\n\t\tmigrated = true;\n\t}\n\treturn migrated;\n}\n\nfunction migrateAuthJsonConfigValues(agentDir: string): ConfigValueMigration[] {\n\tconst authPath = join(agentDir, \"auth.json\");\n\tif (!existsSync(authPath)) return [];\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(authPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return [];\n\t\tconst authData = parsed as Record<string, unknown>;\n\n\t\tconst migrations: ConfigValueMigration[] = [];\n\t\tfor (const [provider, credential] of Object.entries(authData)) {\n\t\t\tif (typeof credential !== \"object\" || credential === null || Array.isArray(credential)) continue;\n\t\t\tconst credentialRecord = credential as Record<string, unknown>;\n\t\t\tif (credentialRecord.type !== \"api_key\") continue;\n\t\t\tmigrateStringProperty(credentialRecord, \"key\", `auth.json[${JSON.stringify(provider)}].key`, migrations);\n\t\t}\n\n\t\tif (migrations.length === 0) return [];\n\t\twriteFileSync(authPath, `${JSON.stringify(parsed, null, 2)}\\n`, \"utf-8\");\n\t\tchmodSync(authPath, 0o600);\n\t\treturn migrations;\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction migrateModelsJsonConfigValues(agentDir: string): ConfigValueMigration[] {\n\tconst modelsPath = join(agentDir, \"models.json\");\n\tif (!existsSync(modelsPath)) return [];\n\n\tconst parsed = JSON.parse(stripJsonComments(readFileSync(modelsPath, \"utf-8\"))) as unknown;\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) return [];\n\tconst modelsData = parsed as Record<string, unknown>;\n\tconst providers = modelsData.providers;\n\tif (typeof providers !== \"object\" || providers === null || Array.isArray(providers)) return [];\n\n\tconst migrations: ConfigValueMigration[] = [];\n\tfor (const [provider, providerConfig] of Object.entries(providers)) {\n\t\tif (typeof providerConfig !== \"object\" || providerConfig === null || Array.isArray(providerConfig)) continue;\n\t\tconst providerRecord = providerConfig as Record<string, unknown>;\n\t\tconst providerLocation = `models.json.providers[${JSON.stringify(provider)}]`;\n\t\tmigrateStringProperty(providerRecord, \"apiKey\", `${providerLocation}.apiKey`, migrations);\n\t\tmigrateHeadersConfig(providerRecord.headers, `${providerLocation}.headers`, migrations);\n\n\t\tif (Array.isArray(providerRecord.models)) {\n\t\t\tfor (let index = 0; index < providerRecord.models.length; index++) {\n\t\t\t\tconst modelConfig = providerRecord.models[index];\n\t\t\t\tif (typeof modelConfig !== \"object\" || modelConfig === null || Array.isArray(modelConfig)) continue;\n\t\t\t\tconst modelRecord = modelConfig as Record<string, unknown>;\n\t\t\t\tconst modelKey = typeof modelRecord.id === \"string\" ? JSON.stringify(modelRecord.id) : String(index);\n\t\t\t\tmigrateHeadersConfig(modelRecord.headers, `${providerLocation}.models[${modelKey}].headers`, migrations);\n\t\t\t}\n\t\t}\n\n\t\tconst modelOverrides = providerRecord.modelOverrides;\n\t\tif (typeof modelOverrides === \"object\" && modelOverrides !== null && !Array.isArray(modelOverrides)) {\n\t\t\tfor (const [modelId, modelOverride] of Object.entries(modelOverrides)) {\n\t\t\t\tif (typeof modelOverride !== \"object\" || modelOverride === null || Array.isArray(modelOverride)) continue;\n\t\t\t\tconst modelOverrideRecord = modelOverride as Record<string, unknown>;\n\t\t\t\tmigrateHeadersConfig(\n\t\t\t\t\tmodelOverrideRecord.headers,\n\t\t\t\t\t`${providerLocation}.modelOverrides[${JSON.stringify(modelId)}].headers`,\n\t\t\t\t\tmigrations,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (migrations.length === 0) return [];\n\twriteFileSync(modelsPath, `${JSON.stringify(parsed, null, 2)}\\n`, \"utf-8\");\n\treturn migrations;\n}\n\nfunction migrateExplicitEnvVarConfigValues(): void {\n\tconst agentDir = getAgentDir();\n\tconst migrations = [...migrateAuthJsonConfigValues(agentDir), ...migrateModelsJsonConfigValues(agentDir)];\n\tif (migrations.length === 0) return;\n\n\tconst details = migrations.map((migration) => ` - ${migration.location}: ${migration.from} -> ${migration.to}`);\n\tconsole.log(\n\t\tchalk.yellow(\n\t\t\t[\n\t\t\t\t\"Warning: Migrated API key/header environment references to explicit $ENV_VAR syntax. Plain strings will be treated as literals.\",\n\t\t\t\t...details,\n\t\t\t].join(\"\\n\"),\n\t\t),\n\t);\n}\n\n/**\n * Migrate sessions from ~/.omk/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.omk/agent/ instead of\n * ~/.omk/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/dmae97/open-multi-agent-kit/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file\n\t\t\tconst fileName = file.split(\"/\").pop() || file.split(\"\\\\\").pop();\n\t\t\tconst newPath = join(correctDir, fileName!);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nconst EXTENSION_SOURCE_EXTENSIONS = new Set([\".ts\", \".js\", \".mts\", \".cts\", \".mjs\", \".cjs\", \".tsx\", \".jsx\"]);\nconst LEGACY_MANIFEST_FIELDS = [\"hooks\", \"customTools\", \"extensions\"] as const;\nconst MAX_LEGACY_EXTENSION_SCAN_DEPTH = 8;\n\nexport type LegacyExtensionClassification = \"legacy\" | \"not-legacy\" | \"unknown\";\n\nexport interface ExtensionDeprecationDiagnostic {\n\tcode: \"LEGACY_EXTENSION_ENTRYPOINT\" | \"LEGACY_EXTENSION_UNKNOWN\";\n\tscope: \"global\" | \"project\";\n\tpath: string;\n\tclassification: LegacyExtensionClassification;\n\tconfidence: number;\n\tevidence: string[];\n\trecommendedAction: \"move-to-extensions\" | \"inspect-manifest\";\n}\n\nexport interface ExtensionMigrationAction {\n\taction: \"move\";\n\tscope: \"global\" | \"project\";\n\tfrom: string;\n\tto: string;\n\tstatus: \"ready\" | \"blocked\" | \"applied\" | \"rolled_back\" | \"in_doubt\";\n\treason: string;\n\tblocker?: string;\n\tsourceStatHash?: string;\n\tsourceContentHash?: string;\n\trecoveryJournalPath?: string;\n\tsourceStat?: {\n\t\tisDirectory: boolean;\n\t\tsize: number;\n\t\tmtimeMs: number;\n\t\tmode: number;\n\t};\n}\n\nexport interface ExtensionMigrationPlan {\n\tdiagnostics: ExtensionDeprecationDiagnostic[];\n\tactions: ExtensionMigrationAction[];\n}\n\nfunction extensionName(name: string): string {\n\tconst dotIndex = name.lastIndexOf(\".\");\n\treturn dotIndex >= 0 ? name.slice(dotIndex) : \"\";\n}\n\nfunction isExtensionSourceFile(name: string): boolean {\n\treturn EXTENSION_SOURCE_EXTENSIONS.has(extensionName(name));\n}\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction manifestEntrypointReferencesSource(value: unknown): boolean {\n\tif (typeof value === \"string\") return isExtensionSourceFile(value);\n\tif (Array.isArray(value)) return value.some((entry) => manifestEntrypointReferencesSource(entry));\n\tif (isObjectRecord(value)) return Object.values(value).some((entry) => manifestEntrypointReferencesSource(entry));\n\treturn false;\n}\n\nfunction inspectLegacyExtensionManifest(\n\tdir: string,\n\tscope: \"global\" | \"project\",\n): ExtensionDeprecationDiagnostic | undefined {\n\tconst packageJsonPath = join(dir, \"package.json\");\n\tif (!existsSync(packageJsonPath)) return undefined;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(packageJsonPath, \"utf-8\")) as unknown;\n\t\tif (!isObjectRecord(parsed)) return undefined;\n\t\tconst manifests = [parsed.pi, parsed.omk].filter(isObjectRecord);\n\t\tconst hasLegacyManifestArray = manifests.some((manifest) =>\n\t\t\tLEGACY_MANIFEST_FIELDS.some((field) => Array.isArray(manifest[field])),\n\t\t);\n\t\tconst hasManifestEntrypoint = [parsed.main, parsed.module, parsed.exports].some(\n\t\t\tmanifestEntrypointReferencesSource,\n\t\t);\n\t\tif (!hasLegacyManifestArray && !hasManifestEntrypoint) return undefined;\n\t\treturn {\n\t\t\tcode: \"LEGACY_EXTENSION_ENTRYPOINT\",\n\t\t\tscope,\n\t\t\tpath: packageJsonPath,\n\t\t\tclassification: \"legacy\",\n\t\t\tconfidence: hasLegacyManifestArray ? 0.96 : 0.82,\n\t\t\tevidence: [hasLegacyManifestArray ? \"manifest legacy array\" : \"manifest entrypoint\"],\n\t\t\trecommendedAction: \"move-to-extensions\",\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tcode: \"LEGACY_EXTENSION_UNKNOWN\",\n\t\t\tscope,\n\t\t\tpath: packageJsonPath,\n\t\t\tclassification: \"unknown\",\n\t\t\tconfidence: 0.5,\n\t\t\tevidence: [\"package.json parse failed\"],\n\t\t\trecommendedAction: \"inspect-manifest\",\n\t\t};\n\t}\n}\n\nfunction isPathWithin(parent: string, child: string): boolean {\n\tconst resolvedParent = resolve(parent);\n\tconst resolvedChild = resolve(child);\n\treturn resolvedChild === resolvedParent || resolvedChild.startsWith(`${resolvedParent}${sep}`);\n}\n\nfunction addLegacySourceDiagnostic(\n\tdiagnostics: ExtensionDeprecationDiagnostic[],\n\tscope: \"global\" | \"project\",\n\tentryPath: string,\n\tevidence: string,\n): void {\n\tdiagnostics.push({\n\t\tcode: \"LEGACY_EXTENSION_ENTRYPOINT\",\n\t\tscope,\n\t\tpath: entryPath,\n\t\tclassification: \"legacy\",\n\t\tconfidence: 0.98,\n\t\tevidence: [evidence],\n\t\trecommendedAction: \"move-to-extensions\",\n\t});\n}\n\nfunction addUnknownExtensionDiagnostic(\n\tdiagnostics: ExtensionDeprecationDiagnostic[],\n\tscope: \"global\" | \"project\",\n\tentryPath: string,\n\tevidence: string,\n\tconfidence = 0.4,\n): void {\n\tdiagnostics.push({\n\t\tcode: \"LEGACY_EXTENSION_UNKNOWN\",\n\t\tscope,\n\t\tpath: entryPath,\n\t\tclassification: \"unknown\",\n\t\tconfidence,\n\t\tevidence: [evidence],\n\t\trecommendedAction: \"inspect-manifest\",\n\t});\n}\n\nfunction collectLegacyExtensionDiagnostics(\n\tentryPath: string,\n\thooksRealPath: string,\n\tscope: \"global\" | \"project\",\n\tdepth: number,\n\tvisitedRealPaths: Set<string>,\n\tdiagnostics: ExtensionDeprecationDiagnostic[],\n): void {\n\tif (depth > MAX_LEGACY_EXTENSION_SCAN_DEPTH) {\n\t\taddUnknownExtensionDiagnostic(diagnostics, scope, entryPath, \"maximum scan depth exceeded\");\n\t\treturn;\n\t}\n\n\tlet currentPath = entryPath;\n\tlet stats: ReturnType<typeof lstatSync>;\n\ttry {\n\t\tstats = lstatSync(currentPath);\n\t\tif (stats.isSymbolicLink()) {\n\t\t\tconst targetPath = realpathSync(currentPath);\n\t\t\tif (!isPathWithin(hooksRealPath, targetPath)) {\n\t\t\t\taddUnknownExtensionDiagnostic(\n\t\t\t\t\tdiagnostics,\n\t\t\t\t\tscope,\n\t\t\t\t\tcurrentPath,\n\t\t\t\t\t\"symlink target outside hooks directory\",\n\t\t\t\t\t0.6,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcurrentPath = targetPath;\n\t\t\tstats = lstatSync(currentPath);\n\t\t}\n\t} catch {\n\t\taddUnknownExtensionDiagnostic(diagnostics, scope, entryPath, \"path stat failed\");\n\t\treturn;\n\t}\n\n\tif (stats.isFile()) {\n\t\tif (isExtensionSourceFile(entryPath) || isExtensionSourceFile(currentPath)) {\n\t\t\taddLegacySourceDiagnostic(diagnostics, scope, entryPath, basename(entryPath));\n\t\t}\n\t\treturn;\n\t}\n\n\tif (!stats.isDirectory()) return;\n\n\tlet directoryRealPath: string;\n\ttry {\n\t\tdirectoryRealPath = realpathSync(currentPath);\n\t} catch {\n\t\taddUnknownExtensionDiagnostic(diagnostics, scope, entryPath, \"directory realpath failed\");\n\t\treturn;\n\t}\n\tif (visitedRealPaths.has(directoryRealPath)) return;\n\tvisitedRealPaths.add(directoryRealPath);\n\n\tconst manifestDiagnostic = inspectLegacyExtensionManifest(currentPath, scope);\n\tif (manifestDiagnostic) diagnostics.push({ ...manifestDiagnostic, path: join(entryPath, \"package.json\") });\n\n\tlet entries: Array<{ name: string }>;\n\ttry {\n\t\tentries = readdirSync(currentPath, { withFileTypes: true });\n\t} catch {\n\t\taddUnknownExtensionDiagnostic(diagnostics, scope, entryPath, \"directory read failed\");\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (entry.name.startsWith(\".\")) continue;\n\t\tcollectLegacyExtensionDiagnostics(\n\t\t\tjoin(entryPath, entry.name),\n\t\t\thooksRealPath,\n\t\t\tscope,\n\t\t\tdepth + 1,\n\t\t\tvisitedRealPaths,\n\t\t\tdiagnostics,\n\t\t);\n\t}\n}\n\nfunction getLegacyExtensionDiagnosticsForHooksDir(\n\thooksDir: string,\n\tscope: \"global\" | \"project\",\n): ExtensionDeprecationDiagnostic[] {\n\tif (!existsSync(hooksDir)) return [];\n\n\ttry {\n\t\tconst hooksRealPath = realpathSync(hooksDir);\n\t\tconst diagnostics: ExtensionDeprecationDiagnostic[] = [];\n\t\tconst visitedRealPaths = new Set<string>();\n\t\tfor (const entry of readdirSync(hooksDir, { withFileTypes: true })) {\n\t\t\tif (entry.name.startsWith(\".\")) continue;\n\t\t\tcollectLegacyExtensionDiagnostics(\n\t\t\t\tjoin(hooksDir, entry.name),\n\t\t\t\thooksRealPath,\n\t\t\t\tscope,\n\t\t\t\t1,\n\t\t\t\tvisitedRealPaths,\n\t\t\t\tdiagnostics,\n\t\t\t);\n\t\t}\n\t\treturn diagnostics;\n\t} catch {\n\t\treturn [\n\t\t\t{\n\t\t\t\tcode: \"LEGACY_EXTENSION_UNKNOWN\",\n\t\t\t\tscope,\n\t\t\t\tpath: hooksDir,\n\t\t\t\tclassification: \"unknown\",\n\t\t\t\tconfidence: 0.4,\n\t\t\t\tevidence: [\"directory read failed\"],\n\t\t\t\trecommendedAction: \"inspect-manifest\",\n\t\t\t},\n\t\t];\n\t}\n}\n\nfunction getExtensionDeprecationScanTargets(cwd: string): Array<{\n\tscope: \"global\" | \"project\";\n\thooksDir: string;\n\textensionsDir: string;\n}> {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\treturn [\n\t\t{\n\t\t\tscope: \"global\",\n\t\t\thooksDir: join(agentDir, \"hooks\"),\n\t\t\textensionsDir: join(agentDir, \"extensions\"),\n\t\t},\n\t\t{\n\t\t\tscope: \"project\",\n\t\t\thooksDir: join(projectDir, \"hooks\"),\n\t\t\textensionsDir: join(projectDir, \"extensions\"),\n\t\t},\n\t];\n}\n\nexport function getExtensionDeprecationDiagnostics(cwd: string): ExtensionDeprecationDiagnostic[] {\n\treturn getExtensionDeprecationScanTargets(cwd).flatMap((target) =>\n\t\tgetLegacyExtensionDiagnosticsForHooksDir(target.hooksDir, target.scope),\n\t);\n}\n\nfunction getMigrationSourcePath(diagnostic: ExtensionDeprecationDiagnostic): string {\n\tconst name = basename(diagnostic.path);\n\tif (name === \"package.json\" || /^index\\.[cm]?[tj]sx?$/.test(name)) {\n\t\treturn dirname(diagnostic.path);\n\t}\n\treturn diagnostic.path;\n}\n\nfunction createMigrationSourceStat(path: string): ExtensionMigrationAction[\"sourceStat\"] | undefined {\n\ttry {\n\t\tconst stats = lstatSync(path);\n\t\treturn {\n\t\t\tisDirectory: stats.isDirectory(),\n\t\t\tsize: stats.size,\n\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\tmode: stats.mode,\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction createMigrationSourceStatTree(path: string, depth = 0): unknown {\n\tconst sourceStat = createMigrationSourceStat(path);\n\tif (!sourceStat) return undefined;\n\tif (!sourceStat.isDirectory || depth >= MAX_LEGACY_EXTENSION_SCAN_DEPTH) {\n\t\treturn { name: basename(path), sourceStat };\n\t}\n\tconst children = readdirSync(path)\n\t\t.filter((entry) => !entry.startsWith(\".\"))\n\t\t.sort()\n\t\t.map((entry) => createMigrationSourceStatTree(join(path, entry), depth + 1));\n\treturn { name: basename(path), sourceStat, children };\n}\n\nfunction createMigrationSourceContentTree(path: string, depth = 0): unknown {\n\tconst sourceStat = createMigrationSourceStat(path);\n\tif (!sourceStat) return undefined;\n\tif (!sourceStat.isDirectory) {\n\t\ttry {\n\t\t\treturn {\n\t\t\t\tname: basename(path),\n\t\t\t\ttype: \"file\",\n\t\t\t\tsha256: createHash(\"sha256\").update(readFileSync(path)).digest(\"hex\"),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\tif (depth >= MAX_LEGACY_EXTENSION_SCAN_DEPTH) return { name: basename(path), type: \"directory\", truncated: true };\n\tconst children = readdirSync(path)\n\t\t.filter((entry) => !entry.startsWith(\".\"))\n\t\t.sort()\n\t\t.map((entry) => createMigrationSourceContentTree(join(path, entry), depth + 1));\n\treturn { name: basename(path), type: \"directory\", children };\n}\n\nfunction hashMigrationSourceStat(path: string): string | undefined {\n\tconst tree = createMigrationSourceStatTree(path);\n\tif (!tree) return undefined;\n\treturn createHash(\"sha256\").update(JSON.stringify(tree)).digest(\"hex\");\n}\n\nfunction hashMigrationSourceContent(path: string): string | undefined {\n\tconst tree = createMigrationSourceContentTree(path);\n\tif (!tree) return undefined;\n\treturn createHash(\"sha256\").update(JSON.stringify(tree)).digest(\"hex\");\n}\n\nfunction addMigrationSourceStat(action: ExtensionMigrationAction): ExtensionMigrationAction {\n\tconst sourceStat = createMigrationSourceStat(action.from);\n\treturn {\n\t\t...action,\n\t\tsourceStat,\n\t\tsourceStatHash: hashMigrationSourceStat(action.from),\n\t\tsourceContentHash: hashMigrationSourceContent(action.from),\n\t};\n}\n\nexport function createExtensionMigrationPlan(cwd: string): ExtensionMigrationPlan {\n\tconst diagnostics = getExtensionDeprecationDiagnostics(cwd);\n\tconst targets = getExtensionDeprecationScanTargets(cwd);\n\tconst actions: ExtensionMigrationAction[] = [];\n\tconst seenSources = new Set<string>();\n\n\tfor (const diagnostic of diagnostics) {\n\t\tif (diagnostic.classification !== \"legacy\") continue;\n\t\tconst target = targets.find((candidate) => candidate.scope === diagnostic.scope);\n\t\tif (!target) continue;\n\t\tconst sourcePath = getMigrationSourcePath(diagnostic);\n\t\tif (!isPathWithin(target.hooksDir, sourcePath)) continue;\n\t\tif ([...seenSources].some((seenSource) => isPathWithin(seenSource, sourcePath))) continue;\n\t\tfor (const seenSource of [...seenSources]) {\n\t\t\tif (isPathWithin(sourcePath, seenSource)) {\n\t\t\t\tseenSources.delete(seenSource);\n\t\t\t\tconst actionIndex = actions.findIndex((action) => action.from === seenSource);\n\t\t\t\tif (actionIndex >= 0) actions.splice(actionIndex, 1);\n\t\t\t}\n\t\t}\n\t\tseenSources.add(sourcePath);\n\t\tconst relativeSource = relative(target.hooksDir, sourcePath);\n\t\tconst destinationPath = join(target.extensionsDir, relativeSource);\n\t\tconst destinationExists = existsSync(destinationPath);\n\t\tactions.push(\n\t\t\taddMigrationSourceStat({\n\t\t\t\taction: \"move\",\n\t\t\t\tscope: diagnostic.scope,\n\t\t\t\tfrom: sourcePath,\n\t\t\t\tto: destinationPath,\n\t\t\t\tstatus: destinationExists ? \"blocked\" : \"ready\",\n\t\t\t\treason: diagnostic.evidence.join(\", \"),\n\t\t\t\tblocker: destinationExists ? \"target already exists\" : undefined,\n\t\t\t}),\n\t\t);\n\t}\n\n\trecordHarnessControlEvent(\"extension.migration.plan\", \"completed\", {\n\t\tdiagnostics: diagnostics.length,\n\t\tlegacyDiagnostics: diagnostics.filter((diagnostic) => diagnostic.classification === \"legacy\").length,\n\t\tunknownDiagnostics: diagnostics.filter((diagnostic) => diagnostic.classification === \"unknown\").length,\n\t\tactions: actions.length,\n\t\tblockedActions: actions.filter((action) => action.status === \"blocked\").length,\n\t});\n\treturn { diagnostics, actions };\n}\n\nexport function applyExtensionMigrationPlan(\n\tcwd: string,\n\tplan: ExtensionMigrationPlan = createExtensionMigrationPlan(cwd),\n): ExtensionMigrationPlan {\n\tconst readyActions = plan.actions.filter((action) => action.status === \"ready\");\n\tconst preflightBlocked = plan.actions.filter((action) => action.status !== \"ready\");\n\tif (preflightBlocked.length > 0) {\n\t\trecordHarnessControlEvent(\"extension.migration.apply\", \"blocked\", {\n\t\t\tactions: plan.actions.length,\n\t\t\tblockedActions: preflightBlocked.length,\n\t\t\treason: \"preflight blocked actions present\",\n\t\t});\n\t\treturn plan;\n\t}\n\n\tconst appliedMoves: Array<{ from: string; to: string }> = [];\n\tlet appliedPlan: ExtensionMigrationPlan = plan;\n\tconst transaction = runHarnessControlTransactionSync({\n\t\tkind: \"extension.migration.apply\",\n\t\tdata: {\n\t\t\tactions: plan.actions.length,\n\t\t\treadyActions: readyActions.length,\n\t\t\tcwd,\n\t\t},\n\t\tbeforeState: plan.actions.map((action) => ({\n\t\t\tfrom: action.from,\n\t\t\tto: action.to,\n\t\t\tstatus: action.status,\n\t\t\tsourceStatHash: action.sourceStatHash,\n\t\t\tsourceContentHash: action.sourceContentHash,\n\t\t})),\n\t\tafterState: () =>\n\t\t\tappliedPlan.actions.map((action) => ({ from: action.from, to: action.to, status: action.status })),\n\t\tcommit: () => {\n\t\t\tfor (const action of readyActions) {\n\t\t\t\tif (!existsSync(action.from)) throw new Error(`source missing: ${action.from}`);\n\t\t\t\tif (existsSync(action.to)) throw new Error(`target already exists: ${action.to}`);\n\t\t\t\tconst currentContentHash = hashMigrationSourceContent(action.from);\n\t\t\t\tif (action.sourceContentHash && currentContentHash !== action.sourceContentHash) {\n\t\t\t\t\tthrow new Error(`source content changed before migration: ${action.from}`);\n\t\t\t\t}\n\t\t\t\tconst currentHash = hashMigrationSourceStat(action.from);\n\t\t\t\tif (action.sourceStatHash && currentHash !== action.sourceStatHash) {\n\t\t\t\t\tthrow new Error(`source changed before migration: ${action.from}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const action of readyActions) {\n\t\t\t\tmkdirSync(dirname(action.to), { recursive: true });\n\t\t\t\trenameSync(action.from, action.to);\n\t\t\t\tappliedMoves.push({ from: action.from, to: action.to });\n\t\t\t}\n\t\t\tappliedPlan = {\n\t\t\t\tdiagnostics: plan.diagnostics,\n\t\t\t\tactions: plan.actions.map((action) =>\n\t\t\t\t\taction.status === \"ready\" ? { ...action, status: \"applied\" as const } : action,\n\t\t\t\t),\n\t\t\t};\n\t\t\treturn appliedPlan;\n\t\t},\n\t\trollback: () => {\n\t\t\tfor (const move of [...appliedMoves].reverse()) {\n\t\t\t\tif (existsSync(move.to) && !existsSync(move.from)) {\n\t\t\t\t\tmkdirSync(dirname(move.from), { recursive: true });\n\t\t\t\t\trenameSync(move.to, move.from);\n\t\t\t\t}\n\t\t\t}\n\t\t\tappliedPlan = {\n\t\t\t\tdiagnostics: plan.diagnostics,\n\t\t\t\tactions: plan.actions.map((action) =>\n\t\t\t\t\taction.status === \"ready\" ? { ...action, status: \"rolled_back\" as const } : action,\n\t\t\t\t),\n\t\t\t};\n\t\t},\n\t});\n\n\tif (transaction.status === \"completed\") return appliedPlan;\n\tconst recoveryJournalPath =\n\t\ttransaction.status === \"in_doubt\" ? join(cwd, \".omk\", \"runs\", \"extension-migration-recovery.json\") : undefined;\n\tif (recoveryJournalPath) {\n\t\tmkdirSync(dirname(recoveryJournalPath), { recursive: true });\n\t\twriteFileSync(\n\t\t\trecoveryJournalPath,\n\t\t\t`${JSON.stringify({ status: \"in_doubt\", appliedMoves, error: String(transaction.error) }, null, 2)}\\n`,\n\t\t\t\"utf-8\",\n\t\t);\n\t}\n\treturn {\n\t\tdiagnostics: plan.diagnostics,\n\t\tactions: plan.actions.map((action) =>\n\t\t\taction.status === \"ready\"\n\t\t\t\t? {\n\t\t\t\t\t\t...action,\n\t\t\t\t\t\tstatus: transaction.status === \"in_doubt\" ? \"in_doubt\" : \"blocked\",\n\t\t\t\t\t\tblocker: transaction.error instanceof Error ? transaction.error.message : String(transaction.error),\n\t\t\t\t\t\trecoveryJournalPath,\n\t\t\t\t\t}\n\t\t\t\t: action,\n\t\t),\n\t};\n}\n\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\tconst scope = label === \"Global\" ? \"global\" : \"project\";\n\n\tif (\n\t\tgetLegacyExtensionDiagnosticsForHooksDir(hooksDir, scope).some(\n\t\t\t(diagnostic) => diagnostic.classification === \"legacy\",\n\t\t)\n\t) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\tawait new Promise<void>((resolve) => {\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", () => {\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tresolve();\n\t\t});\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateExplicitEnvVarConfigValues();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}