@adhdev/daemon-core 0.9.82-rc.15 → 0.9.82-rc.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +5 -1
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +104 -63
- package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +27 -0
- package/dist/commands/handler.d.ts +66 -0
- package/dist/commands/router.d.ts +22 -0
- package/dist/config/chat-history.d.ts +5 -0
- package/dist/config/config.d.ts +5 -0
- package/dist/config/mesh-config.d.ts +68 -1
- package/dist/git/git-commands.d.ts +5 -1
- package/dist/index.d.ts +20 -6
- package/dist/index.js +13153 -3031
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13099 -3023
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/mesh/beads-db.d.ts +72 -0
- package/dist/mesh/contracts.d.ts +164 -0
- package/dist/mesh/coordinator-registry.d.ts +25 -0
- package/dist/mesh/mesh-active-work.d.ts +90 -0
- package/dist/mesh/mesh-events.d.ts +77 -5
- package/dist/mesh/mesh-fast-forward.d.ts +39 -0
- package/dist/mesh/mesh-host-ownership.d.ts +9 -0
- package/dist/mesh/mesh-ledger.d.ts +58 -1
- package/dist/mesh/mesh-refine-status.d.ts +26 -0
- package/dist/mesh/mesh-work-queue.d.ts +44 -5
- package/dist/mesh/preview-freshness.d.ts +18 -0
- package/dist/mesh/refine-config.d.ts +193 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
- package/dist/providers/approval-utils.d.ts +9 -0
- package/dist/providers/cli-provider-instance.d.ts +6 -1
- package/dist/providers/contracts.d.ts +50 -5
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/index.d.ts +11 -0
- package/dist/providers/provider-loader.d.ts +19 -1
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +64 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +169 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/repo-mesh-types.d.ts +67 -0
- package/dist/shared-types.d.ts +12 -0
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +6 -2
- package/src/boot/daemon-lifecycle.ts +17 -10
- package/src/boot/process-hardening.ts +89 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +14 -1
- package/src/cli-adapter-types.d.ts +1 -0
- package/src/cli-adapter-types.ts +3 -1
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1086 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +664 -1137
- package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
- package/src/cli-adapters/provider-cli-parse.ts +13 -0
- package/src/cli-adapters/provider-cli-runtime.ts +3 -1
- package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
- package/src/cli-adapters/provider-cli-shared.ts +57 -11
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
- package/src/commands/chat-commands.ts +1451 -52
- package/src/commands/cli-manager.ts +184 -3
- package/src/commands/handler.ts +547 -2
- package/src/commands/mesh-coordinator.ts +13 -143
- package/src/commands/router.ts +3273 -428
- package/src/config/chat-history.ts +79 -24
- package/src/config/config.ts +12 -0
- package/src/config/mesh-config.ts +249 -2
- package/src/config/recent-activity.ts +8 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/detection/ide-detector.ts +26 -16
- package/src/git/git-commands.ts +17 -5
- package/src/git/git-worktree.ts +8 -1
- package/src/index.ts +72 -5
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/logger.ts +2 -1
- package/src/mesh/beads-db.ts +479 -0
- package/src/mesh/contracts.ts +329 -0
- package/src/mesh/coordinator-prompt.ts +40 -22
- package/src/mesh/coordinator-registry.ts +75 -0
- package/src/mesh/mesh-active-work.ts +437 -0
- package/src/mesh/mesh-events.ts +820 -61
- package/src/mesh/mesh-fast-forward.ts +430 -0
- package/src/mesh/mesh-host-ownership.ts +73 -0
- package/src/mesh/mesh-ledger.ts +457 -104
- package/src/mesh/mesh-refine-status.ts +144 -0
- package/src/mesh/mesh-work-queue.ts +216 -158
- package/src/mesh/preview-freshness.ts +118 -0
- package/src/mesh/refine-config.ts +366 -0
- package/src/mesh/worktree-bootstrap-config.ts +247 -0
- package/src/providers/approval-utils.ts +39 -5
- package/src/providers/chat-message-normalization.ts +4 -11
- package/src/providers/cli-provider-instance.ts +380 -44
- package/src/providers/contracts.ts +52 -5
- package/src/providers/ide-provider-instance.ts +17 -3
- package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +419 -0
- package/src/providers/native-history/index.ts +23 -0
- package/src/providers/provider-loader.ts +294 -28
- package/src/providers/provider-schema.ts +31 -13
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +245 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +444 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +119 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +210 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/version-archive.ts +38 -20
- package/src/repo-mesh-types.ts +77 -0
- package/src/shared-types.ts +9 -0
- package/src/status/builders.ts +23 -6
- package/src/status/reporter.ts +15 -0
- package/src/status/snapshot.ts +35 -11
- package/src/system/host-memory.ts +29 -12
- package/src/types.ts +5 -0
|
@@ -1345,7 +1345,15 @@ function rewriteCanonicalSavedHistory(agentType: string, historySessionId: strin
|
|
|
1345
1345
|
|
|
1346
1346
|
export type ProviderNativeHistoryScripts = Record<string, ((input: any) => any) | undefined>;
|
|
1347
1347
|
|
|
1348
|
-
type ProviderNativeHistoryReadResult = {
|
|
1348
|
+
type ProviderNativeHistoryReadResult = {
|
|
1349
|
+
records: HistoryMessage[];
|
|
1350
|
+
sourcePath: string;
|
|
1351
|
+
sourceMtimeMs: number;
|
|
1352
|
+
providerSessionId?: string;
|
|
1353
|
+
nativeHistoryCoverage?: string;
|
|
1354
|
+
partialReason?: string;
|
|
1355
|
+
unavailableReason?: string;
|
|
1356
|
+
};
|
|
1349
1357
|
|
|
1350
1358
|
function getNativeHistoryScriptName(canonicalHistory: ProviderCanonicalHistoryConfig | undefined, key: 'readSession' | 'listSessions'): string {
|
|
1351
1359
|
const configured = canonicalHistory?.scripts?.[key];
|
|
@@ -1367,19 +1375,42 @@ function normalizeProviderNativeHistoryRecords(agentType: string, historySession
|
|
|
1367
1375
|
if (!Array.isArray(records)) return [];
|
|
1368
1376
|
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId);
|
|
1369
1377
|
return records
|
|
1370
|
-
.map((record: any) =>
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1378
|
+
.map((record: any) => {
|
|
1379
|
+
const base: HistoryMessage = {
|
|
1380
|
+
ts: typeof record?.ts === 'string' ? record.ts : new Date(Number(record?.receivedAt) || Date.now()).toISOString(),
|
|
1381
|
+
receivedAt: Number(record?.receivedAt) || Date.parse(record?.ts || '') || Date.now(),
|
|
1382
|
+
role: record?.role,
|
|
1383
|
+
content: String(record?.content || ''),
|
|
1384
|
+
kind: record?.kind || (record?.role === 'system' ? 'session_start' : 'standard'),
|
|
1385
|
+
senderName: record?.senderName,
|
|
1386
|
+
agent: agentType,
|
|
1387
|
+
instanceId: record?.instanceId,
|
|
1388
|
+
historySessionId: normalizeSavedHistorySessionId(record?.historySessionId || normalizedSessionId),
|
|
1389
|
+
sessionTitle: record?.sessionTitle,
|
|
1390
|
+
workspace: record?.workspace,
|
|
1391
|
+
} as HistoryMessage;
|
|
1392
|
+
// (A2.3 v2 identity passthrough) — if the producer (native_history.js)
|
|
1393
|
+
// emitted v2 stable identity, keep it across the sanitize layer so
|
|
1394
|
+
// downstream (chat-commands.ts normalizeNativeHistoryMessages) sees
|
|
1395
|
+
// the producer's contract output instead of recomputing from index
|
|
1396
|
+
// and content hash. v1 producers without these fields are unaffected.
|
|
1397
|
+
if (typeof record?.providerUnitKey === 'string' && record.providerUnitKey) {
|
|
1398
|
+
(base as any).providerUnitKey = record.providerUnitKey;
|
|
1399
|
+
}
|
|
1400
|
+
if (typeof record?.bubbleId === 'string' && record.bubbleId) {
|
|
1401
|
+
(base as any).bubbleId = record.bubbleId;
|
|
1402
|
+
}
|
|
1403
|
+
if (typeof record?.sequence === 'number' && Number.isFinite(record.sequence)) {
|
|
1404
|
+
(base as any).sequence = record.sequence;
|
|
1405
|
+
}
|
|
1406
|
+
if (typeof record?._turnKey === 'string' && record._turnKey) {
|
|
1407
|
+
(base as any)._turnKey = record._turnKey;
|
|
1408
|
+
}
|
|
1409
|
+
if (typeof record?.bubbleState === 'string' && record.bubbleState) {
|
|
1410
|
+
(base as any).bubbleState = record.bubbleState;
|
|
1411
|
+
}
|
|
1412
|
+
return sanitizeHistoryMessage(agentType, base);
|
|
1413
|
+
})
|
|
1383
1414
|
.filter(Boolean) as HistoryMessage[];
|
|
1384
1415
|
}
|
|
1385
1416
|
|
|
@@ -1387,27 +1418,34 @@ function callProviderNativeHistoryRead(
|
|
|
1387
1418
|
agentType: string,
|
|
1388
1419
|
canonicalHistory: ProviderCanonicalHistoryConfig | undefined,
|
|
1389
1420
|
scripts: ProviderNativeHistoryScripts | undefined,
|
|
1390
|
-
historySessionId: string,
|
|
1421
|
+
historySessionId: string | undefined,
|
|
1391
1422
|
workspace?: string,
|
|
1423
|
+
excludeInProgressTurn?: boolean,
|
|
1392
1424
|
): ProviderNativeHistoryReadResult | null {
|
|
1393
1425
|
const fn = getProviderNativeHistoryScript(scripts, canonicalHistory, 'readSession');
|
|
1394
1426
|
if (!fn) return null;
|
|
1427
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
1395
1428
|
const result = fn({
|
|
1396
1429
|
agentType,
|
|
1397
|
-
sessionId:
|
|
1398
|
-
historySessionId,
|
|
1430
|
+
sessionId: normalizedSessionId,
|
|
1431
|
+
historySessionId: normalizedSessionId,
|
|
1399
1432
|
workspace,
|
|
1400
1433
|
format: canonicalHistory?.format,
|
|
1401
1434
|
watchPath: canonicalHistory?.watchPath,
|
|
1402
|
-
|
|
1435
|
+
excludeInProgressTurn: excludeInProgressTurn === true,
|
|
1436
|
+
args: { sessionId: normalizedSessionId, historySessionId: normalizedSessionId, workspace, excludeInProgressTurn: excludeInProgressTurn === true },
|
|
1403
1437
|
});
|
|
1404
1438
|
if (!result || typeof result !== 'object') return null;
|
|
1405
|
-
const records = normalizeProviderNativeHistoryRecords(agentType,
|
|
1439
|
+
const records = normalizeProviderNativeHistoryRecords(agentType, normalizedSessionId, (result as any).messages || (result as any).records);
|
|
1406
1440
|
if (records.length === 0) return null;
|
|
1407
1441
|
return {
|
|
1408
1442
|
records,
|
|
1409
1443
|
sourcePath: typeof (result as any).sourcePath === 'string' ? (result as any).sourcePath : '',
|
|
1410
1444
|
sourceMtimeMs: Number((result as any).sourceMtimeMs) || 0,
|
|
1445
|
+
providerSessionId: typeof (result as any).providerSessionId === 'string' ? (result as any).providerSessionId.trim() : undefined,
|
|
1446
|
+
nativeHistoryCoverage: typeof (result as any).nativeHistoryCoverage === 'string' ? (result as any).nativeHistoryCoverage.trim() : undefined,
|
|
1447
|
+
partialReason: typeof (result as any).partialReason === 'string' ? (result as any).partialReason.trim() : undefined,
|
|
1448
|
+
unavailableReason: typeof (result as any).unavailableReason === 'string' ? (result as any).unavailableReason.trim() : undefined,
|
|
1411
1449
|
};
|
|
1412
1450
|
}
|
|
1413
1451
|
|
|
@@ -1417,10 +1455,12 @@ function buildNativeHistoryReadResult(
|
|
|
1417
1455
|
scripts: ProviderNativeHistoryScripts | undefined,
|
|
1418
1456
|
historySessionId: string | undefined,
|
|
1419
1457
|
workspace?: string,
|
|
1458
|
+
excludeInProgressTurn?: boolean,
|
|
1420
1459
|
): ProviderNativeHistoryReadResult | null {
|
|
1421
1460
|
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
1422
|
-
|
|
1423
|
-
|
|
1461
|
+
const normalizedWorkspace = typeof workspace === 'string' ? workspace.trim() : '';
|
|
1462
|
+
if (!canonicalHistory || (!normalizedSessionId && !normalizedWorkspace) || !isNativeSourceCanonicalHistory(canonicalHistory)) return null;
|
|
1463
|
+
return callProviderNativeHistoryRead(agentType, canonicalHistory, scripts, normalizedSessionId, workspace, excludeInProgressTurn);
|
|
1424
1464
|
}
|
|
1425
1465
|
|
|
1426
1466
|
function materializeNativeHistoryToMirror(
|
|
@@ -1476,16 +1516,31 @@ export function readProviderChatHistory(
|
|
|
1476
1516
|
excludeRecentCount?: number;
|
|
1477
1517
|
historyBehavior?: ProviderHistoryBehavior;
|
|
1478
1518
|
scripts?: ProviderNativeHistoryScripts;
|
|
1519
|
+
excludeInProgressTurn?: boolean;
|
|
1479
1520
|
} = {},
|
|
1480
|
-
): {
|
|
1481
|
-
|
|
1482
|
-
|
|
1521
|
+
): {
|
|
1522
|
+
messages: HistoryMessage[];
|
|
1523
|
+
hasMore: boolean;
|
|
1524
|
+
source: 'provider-native' | 'adhdev-mirror' | 'native-unavailable';
|
|
1525
|
+
sourcePath?: string;
|
|
1526
|
+
sourceMtimeMs?: number;
|
|
1527
|
+
providerSessionId?: string;
|
|
1528
|
+
nativeHistoryCoverage?: string;
|
|
1529
|
+
partialReason?: string;
|
|
1530
|
+
unavailableReason?: string;
|
|
1531
|
+
} {
|
|
1532
|
+
if (isNativeSourceCanonicalHistory(options.canonicalHistory) && (options.historySessionId || options.workspace)) {
|
|
1533
|
+
const nativeResult = buildNativeHistoryReadResult(agentType, options.canonicalHistory, options.scripts, options.historySessionId, options.workspace, options.excludeInProgressTurn);
|
|
1483
1534
|
if (!nativeResult) return { messages: [], hasMore: false, source: 'native-unavailable' };
|
|
1484
1535
|
return {
|
|
1485
1536
|
...pageHistoryRecords(agentType, nativeResult.records, options.offset || 0, options.limit || 30, options.excludeRecentCount || 0, options.historyBehavior),
|
|
1486
1537
|
source: 'provider-native',
|
|
1487
1538
|
sourcePath: nativeResult.sourcePath,
|
|
1488
1539
|
sourceMtimeMs: nativeResult.sourceMtimeMs,
|
|
1540
|
+
providerSessionId: nativeResult.providerSessionId,
|
|
1541
|
+
nativeHistoryCoverage: nativeResult.nativeHistoryCoverage,
|
|
1542
|
+
partialReason: nativeResult.partialReason,
|
|
1543
|
+
unavailableReason: nativeResult.unavailableReason,
|
|
1489
1544
|
};
|
|
1490
1545
|
}
|
|
1491
1546
|
return {
|
package/src/config/config.ts
CHANGED
|
@@ -272,6 +272,18 @@ export function getConfigDir(): string {
|
|
|
272
272
|
return dir;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Get the daemon runtime data directory (~/.adhdev/daemon/).
|
|
277
|
+
* Distinct from the user-config dir so runtime state can be cleared independently.
|
|
278
|
+
*/
|
|
279
|
+
export function getDaemonDataDir(): string {
|
|
280
|
+
const dir = join(getConfigDir(), 'daemon');
|
|
281
|
+
if (!existsSync(dir)) {
|
|
282
|
+
mkdirSync(dir, { recursive: true });
|
|
283
|
+
}
|
|
284
|
+
return dir;
|
|
285
|
+
}
|
|
286
|
+
|
|
275
287
|
/**
|
|
276
288
|
* Get the config file path
|
|
277
289
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
10
|
import { join } from 'path';
|
|
11
|
-
import { randomUUID } from 'crypto';
|
|
11
|
+
import { createHash, randomBytes, randomUUID } from 'crypto';
|
|
12
12
|
import { getConfigDir } from './config.js';
|
|
13
13
|
import type {
|
|
14
14
|
LocalMeshConfig,
|
|
@@ -18,8 +18,11 @@ import type {
|
|
|
18
18
|
RepoMeshNodePolicy,
|
|
19
19
|
RepoMeshNodeCapabilities,
|
|
20
20
|
RepoMeshCoordinatorConfig,
|
|
21
|
+
RepoMeshHostMetadata,
|
|
22
|
+
RepoMeshDaemonRole,
|
|
21
23
|
} from '../repo-mesh-types.js';
|
|
22
24
|
import { DEFAULT_MESH_POLICY } from '../repo-mesh-types.js';
|
|
25
|
+
import { createDefaultMeshHostMetadata } from '../mesh/mesh-host-ownership.js';
|
|
23
26
|
|
|
24
27
|
// ─── Persistence ────────────────────────────────
|
|
25
28
|
|
|
@@ -84,6 +87,7 @@ function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMe
|
|
|
84
87
|
}
|
|
85
88
|
const maxParallelTasks = Number(policy.maxParallelTasks);
|
|
86
89
|
policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
|
|
90
|
+
policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
|
|
87
91
|
if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
|
|
88
92
|
policy.sessionCleanupOnNodeRemove = 'preserve';
|
|
89
93
|
}
|
|
@@ -112,6 +116,7 @@ export interface CreateMeshOptions {
|
|
|
112
116
|
defaultBranch?: string;
|
|
113
117
|
policy?: Partial<RepoMeshPolicy>;
|
|
114
118
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
119
|
+
meshHost?: RepoMeshHostMetadata;
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
@@ -133,6 +138,7 @@ export function createMesh(opts: CreateMeshOptions): LocalMeshEntry {
|
|
|
133
138
|
defaultBranch: opts.defaultBranch,
|
|
134
139
|
policy: mergeMeshPolicy(undefined, opts.policy),
|
|
135
140
|
coordinator: opts.coordinator || {},
|
|
141
|
+
meshHost: opts.meshHost || createDefaultMeshHostMetadata(),
|
|
136
142
|
nodes: [],
|
|
137
143
|
createdAt: now,
|
|
138
144
|
updatedAt: now,
|
|
@@ -148,6 +154,7 @@ export interface UpdateMeshOptions {
|
|
|
148
154
|
defaultBranch?: string;
|
|
149
155
|
policy?: Partial<RepoMeshPolicy>;
|
|
150
156
|
coordinator?: RepoMeshCoordinatorConfig;
|
|
157
|
+
meshHost?: RepoMeshHostMetadata;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEntry | undefined {
|
|
@@ -159,6 +166,7 @@ export function updateMesh(meshId: string, opts: UpdateMeshOptions): LocalMeshEn
|
|
|
159
166
|
if (opts.defaultBranch !== undefined) mesh.defaultBranch = opts.defaultBranch;
|
|
160
167
|
if (opts.policy) mesh.policy = mergeMeshPolicy(mesh.policy, opts.policy);
|
|
161
168
|
if (opts.coordinator) mesh.coordinator = opts.coordinator;
|
|
169
|
+
if (opts.meshHost) mesh.meshHost = opts.meshHost;
|
|
162
170
|
mesh.updatedAt = new Date().toISOString();
|
|
163
171
|
|
|
164
172
|
saveMeshConfig(config);
|
|
@@ -174,6 +182,240 @@ export function deleteMesh(meshId: string): boolean {
|
|
|
174
182
|
return true;
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
function normalizeManualHostAddress(hostAddress: string): string {
|
|
186
|
+
const normalized = hostAddress.trim().replace(/\/+$/, '');
|
|
187
|
+
if (!normalized) throw new Error('hostAddress required');
|
|
188
|
+
let parsed: URL;
|
|
189
|
+
try {
|
|
190
|
+
parsed = new URL(normalized);
|
|
191
|
+
} catch {
|
|
192
|
+
throw new Error('hostAddress must be a valid http(s) or ws(s) URL');
|
|
193
|
+
}
|
|
194
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol)) {
|
|
195
|
+
throw new Error('hostAddress must use http, https, ws, or wss');
|
|
196
|
+
}
|
|
197
|
+
return normalized;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function tokenIdForManualPairing(token: string): string {
|
|
201
|
+
return `tok_${createHash('sha256').update(token).digest('hex').slice(0, 16)}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeTokenExpiry(value: unknown): string | undefined {
|
|
205
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
206
|
+
const date = new Date(value);
|
|
207
|
+
if (Number.isNaN(date.getTime())) throw new Error('expiresAt must be a valid ISO date');
|
|
208
|
+
return date.toISOString();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function assertPairingTokenValid(pairing: RepoMeshHostMetadata['pairing'], rawToken: string, nowIso: string): { ok: true; tokenId: string } | { ok: false; reason: string; expectedTokenId?: string; presentedTokenId?: string } {
|
|
212
|
+
const token = rawToken.trim();
|
|
213
|
+
if (!token) return { ok: false, reason: 'token required' };
|
|
214
|
+
const presentedTokenId = tokenIdForManualPairing(token);
|
|
215
|
+
const expectedTokenId = pairing?.tokenId;
|
|
216
|
+
if (!expectedTokenId || pairing?.status === 'not_configured' || pairing?.status === 'revoked') {
|
|
217
|
+
return { ok: false, reason: 'host pairing token is not configured', presentedTokenId };
|
|
218
|
+
}
|
|
219
|
+
if (pairing.expiresAt && new Date(pairing.expiresAt).getTime() <= new Date(nowIso).getTime()) {
|
|
220
|
+
return { ok: false, reason: 'host pairing token expired', expectedTokenId, presentedTokenId };
|
|
221
|
+
}
|
|
222
|
+
if (presentedTokenId !== expectedTokenId) {
|
|
223
|
+
return { ok: false, reason: 'invalid pairing token', expectedTokenId, presentedTokenId };
|
|
224
|
+
}
|
|
225
|
+
return { ok: true, tokenId: presentedTokenId };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface ConfigureMeshHostPairingOptions {
|
|
229
|
+
hostAddress: string;
|
|
230
|
+
token: string;
|
|
231
|
+
now?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function configureMeshHostPairing(
|
|
235
|
+
meshId: string,
|
|
236
|
+
opts: ConfigureMeshHostPairingOptions,
|
|
237
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; hostAddress: string } | undefined {
|
|
238
|
+
const hostAddress = normalizeManualHostAddress(opts.hostAddress);
|
|
239
|
+
const token = opts.token.trim();
|
|
240
|
+
if (!token) throw new Error('token required');
|
|
241
|
+
|
|
242
|
+
const config = loadMeshConfig();
|
|
243
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
244
|
+
if (!mesh) return undefined;
|
|
245
|
+
|
|
246
|
+
const now = opts.now || new Date().toISOString();
|
|
247
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
248
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
249
|
+
...previous,
|
|
250
|
+
role: 'member',
|
|
251
|
+
hostAddress,
|
|
252
|
+
pairing: {
|
|
253
|
+
status: 'pairing',
|
|
254
|
+
tokenId: tokenIdForManualPairing(token),
|
|
255
|
+
lastPairedAt: now,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
mesh.meshHost = meshHost;
|
|
260
|
+
mesh.updatedAt = now;
|
|
261
|
+
saveMeshConfig(config);
|
|
262
|
+
return { mesh, meshHost, hostAddress };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface CreateMeshHostPairingTokenOptions {
|
|
266
|
+
token?: string;
|
|
267
|
+
expiresAt?: string;
|
|
268
|
+
now?: string;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function createMeshHostPairingToken(
|
|
272
|
+
meshId: string,
|
|
273
|
+
opts: CreateMeshHostPairingTokenOptions = {},
|
|
274
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; token: string; tokenId: string; expiresAt?: string } | undefined {
|
|
275
|
+
const config = loadMeshConfig();
|
|
276
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
277
|
+
if (!mesh) return undefined;
|
|
278
|
+
const now = opts.now || new Date().toISOString();
|
|
279
|
+
const token = (opts.token || `mhj_${randomBytes(24).toString('base64url')}`).trim();
|
|
280
|
+
if (!token) throw new Error('token required');
|
|
281
|
+
const tokenId = tokenIdForManualPairing(token);
|
|
282
|
+
const expiresAt = normalizeTokenExpiry(opts.expiresAt);
|
|
283
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
284
|
+
if (previous.role === 'member') {
|
|
285
|
+
throw new Error('Mesh Host daemon required to create host pairing tokens; member daemons cannot mint host join tokens.');
|
|
286
|
+
}
|
|
287
|
+
const meshHost: RepoMeshHostMetadata = {
|
|
288
|
+
...previous,
|
|
289
|
+
role: 'host',
|
|
290
|
+
pairing: {
|
|
291
|
+
status: 'pairing',
|
|
292
|
+
tokenId,
|
|
293
|
+
lastPairedAt: now,
|
|
294
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
mesh.meshHost = meshHost;
|
|
298
|
+
mesh.updatedAt = now;
|
|
299
|
+
saveMeshConfig(config);
|
|
300
|
+
return { mesh, meshHost, token, tokenId, ...(expiresAt ? { expiresAt } : {}) };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface MeshHostJoinMemberNodeInput {
|
|
304
|
+
id?: string;
|
|
305
|
+
workspace: string;
|
|
306
|
+
repoRoot?: string;
|
|
307
|
+
daemonId?: string;
|
|
308
|
+
machineId?: string;
|
|
309
|
+
userOverrides?: Partial<RepoMeshNodeCapabilities>;
|
|
310
|
+
policy?: RepoMeshNodePolicy;
|
|
311
|
+
role?: RepoMeshDaemonRole;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface ApplyMeshHostJoinOptions {
|
|
315
|
+
token: string;
|
|
316
|
+
memberNode: MeshHostJoinMemberNodeInput;
|
|
317
|
+
memberMeshId?: string;
|
|
318
|
+
now?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function applyMeshHostJoinRequest(
|
|
322
|
+
meshId: string,
|
|
323
|
+
opts: ApplyMeshHostJoinOptions,
|
|
324
|
+
): { accepted: true; mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata; node: LocalMeshNodeEntry; tokenId: string } | { accepted: false; mesh?: LocalMeshEntry; meshHost?: RepoMeshHostMetadata; tokenId?: string; reason: string } | undefined {
|
|
325
|
+
const config = loadMeshConfig();
|
|
326
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
327
|
+
if (!mesh) return undefined;
|
|
328
|
+
const now = opts.now || new Date().toISOString();
|
|
329
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
330
|
+
if (previous.role === 'member') {
|
|
331
|
+
return { accepted: false, mesh, meshHost: previous, reason: 'Mesh Host daemon required to accept join requests' };
|
|
332
|
+
}
|
|
333
|
+
const meshHost: RepoMeshHostMetadata = { ...previous, role: 'host' };
|
|
334
|
+
const validation = assertPairingTokenValid(meshHost.pairing, opts.token, now);
|
|
335
|
+
if (!validation.ok) {
|
|
336
|
+
mesh.meshHost = {
|
|
337
|
+
...meshHost,
|
|
338
|
+
pairing: {
|
|
339
|
+
...(meshHost.pairing || { status: 'not_configured' as const }),
|
|
340
|
+
status: 'rejected',
|
|
341
|
+
lastRejectedAt: now,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
mesh.updatedAt = now;
|
|
345
|
+
saveMeshConfig(config);
|
|
346
|
+
return { accepted: false, mesh, meshHost: mesh.meshHost, tokenId: validation.presentedTokenId, reason: validation.reason };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const workspace = opts.memberNode.workspace.trim();
|
|
350
|
+
if (!workspace) throw new Error('memberNode.workspace required');
|
|
351
|
+
const memberId = opts.memberNode.id?.trim();
|
|
352
|
+
let node = mesh.nodes.find(n => (memberId && n.id === memberId) || n.workspace === workspace);
|
|
353
|
+
if (node) {
|
|
354
|
+
node.workspace = workspace;
|
|
355
|
+
node.repoRoot = opts.memberNode.repoRoot;
|
|
356
|
+
node.daemonId = opts.memberNode.daemonId;
|
|
357
|
+
node.machineId = opts.memberNode.machineId;
|
|
358
|
+
node.userOverrides = opts.memberNode.userOverrides || node.userOverrides || {};
|
|
359
|
+
node.policy = { ...(node.policy || {}), ...(opts.memberNode.policy || {}) };
|
|
360
|
+
node.role = 'member';
|
|
361
|
+
} else {
|
|
362
|
+
if (mesh.nodes.length >= 10) throw new Error('Maximum 10 nodes per mesh');
|
|
363
|
+
node = {
|
|
364
|
+
id: memberId || `node_${randomUUID().replace(/-/g, '')}`,
|
|
365
|
+
workspace,
|
|
366
|
+
repoRoot: opts.memberNode.repoRoot,
|
|
367
|
+
daemonId: opts.memberNode.daemonId,
|
|
368
|
+
machineId: opts.memberNode.machineId,
|
|
369
|
+
userOverrides: opts.memberNode.userOverrides || {},
|
|
370
|
+
policy: opts.memberNode.policy || {},
|
|
371
|
+
role: 'member',
|
|
372
|
+
};
|
|
373
|
+
mesh.nodes.push(node);
|
|
374
|
+
}
|
|
375
|
+
mesh.meshHost = {
|
|
376
|
+
...meshHost,
|
|
377
|
+
pairing: {
|
|
378
|
+
...(meshHost.pairing || {}),
|
|
379
|
+
status: 'paired',
|
|
380
|
+
tokenId: validation.tokenId,
|
|
381
|
+
joinedAt: now,
|
|
382
|
+
lastPairedAt: meshHost.pairing?.lastPairedAt || now,
|
|
383
|
+
...(meshHost.pairing?.expiresAt ? { expiresAt: meshHost.pairing.expiresAt } : {}),
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
mesh.updatedAt = now;
|
|
387
|
+
saveMeshConfig(config);
|
|
388
|
+
return { accepted: true, mesh, meshHost: mesh.meshHost, node, tokenId: validation.tokenId };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function markMeshHostPairingJoined(
|
|
392
|
+
meshId: string,
|
|
393
|
+
opts: { hostDaemonId?: string; hostNodeId?: string; joinedAt?: string; token?: string; tokenId?: string },
|
|
394
|
+
): { mesh: LocalMeshEntry; meshHost: RepoMeshHostMetadata } | undefined {
|
|
395
|
+
const config = loadMeshConfig();
|
|
396
|
+
const mesh = config.meshes.find(m => m.id === meshId);
|
|
397
|
+
if (!mesh) return undefined;
|
|
398
|
+
const now = opts.joinedAt || new Date().toISOString();
|
|
399
|
+
const previous = mesh.meshHost || createDefaultMeshHostMetadata();
|
|
400
|
+
const tokenId = opts.tokenId || (opts.token ? tokenIdForManualPairing(opts.token) : previous.pairing?.tokenId);
|
|
401
|
+
mesh.meshHost = {
|
|
402
|
+
...previous,
|
|
403
|
+
role: 'member',
|
|
404
|
+
...(opts.hostDaemonId ? { hostDaemonId: opts.hostDaemonId } : {}),
|
|
405
|
+
...(opts.hostNodeId ? { hostNodeId: opts.hostNodeId } : {}),
|
|
406
|
+
pairing: {
|
|
407
|
+
...(previous.pairing || {}),
|
|
408
|
+
status: 'paired',
|
|
409
|
+
...(tokenId ? { tokenId } : {}),
|
|
410
|
+
joinedAt: now,
|
|
411
|
+
lastPairedAt: previous.pairing?.lastPairedAt || now,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
mesh.updatedAt = now;
|
|
415
|
+
saveMeshConfig(config);
|
|
416
|
+
return { mesh, meshHost: mesh.meshHost };
|
|
417
|
+
}
|
|
418
|
+
|
|
177
419
|
// ─── Node Operations ────────────────────────────
|
|
178
420
|
|
|
179
421
|
export interface AddNodeOptions {
|
|
@@ -186,6 +428,8 @@ export interface AddNodeOptions {
|
|
|
186
428
|
isLocalWorktree?: boolean;
|
|
187
429
|
worktreeBranch?: string;
|
|
188
430
|
clonedFromNodeId?: string;
|
|
431
|
+
worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'];
|
|
432
|
+
role?: RepoMeshDaemonRole;
|
|
189
433
|
}
|
|
190
434
|
|
|
191
435
|
export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntry | undefined {
|
|
@@ -213,6 +457,8 @@ export function addNode(meshId: string, opts: AddNodeOptions): LocalMeshNodeEntr
|
|
|
213
457
|
isLocalWorktree: opts.isLocalWorktree,
|
|
214
458
|
worktreeBranch: opts.worktreeBranch,
|
|
215
459
|
clonedFromNodeId: opts.clonedFromNodeId,
|
|
460
|
+
worktreeBootstrap: opts.worktreeBootstrap,
|
|
461
|
+
role: opts.role,
|
|
216
462
|
};
|
|
217
463
|
|
|
218
464
|
mesh.nodes.push(node);
|
|
@@ -238,7 +484,7 @@ export function removeNode(meshId: string, nodeId: string): boolean {
|
|
|
238
484
|
export function updateNode(
|
|
239
485
|
meshId: string,
|
|
240
486
|
nodeId: string,
|
|
241
|
-
opts: { userOverrides?: Partial<RepoMeshNodeCapabilities>; policy?: RepoMeshNodePolicy },
|
|
487
|
+
opts: { userOverrides?: Partial<RepoMeshNodeCapabilities>; policy?: RepoMeshNodePolicy; worktreeBootstrap?: LocalMeshNodeEntry['worktreeBootstrap'] },
|
|
242
488
|
): LocalMeshNodeEntry | undefined {
|
|
243
489
|
const config = loadMeshConfig();
|
|
244
490
|
const mesh = config.meshes.find(m => m.id === meshId);
|
|
@@ -249,6 +495,7 @@ export function updateNode(
|
|
|
249
495
|
|
|
250
496
|
if (opts.userOverrides) node.userOverrides = { ...node.userOverrides, ...opts.userOverrides };
|
|
251
497
|
if (opts.policy) node.policy = { ...node.policy, ...opts.policy };
|
|
498
|
+
if (opts.worktreeBootstrap) node.worktreeBootstrap = opts.worktreeBootstrap;
|
|
252
499
|
mesh.updatedAt = new Date().toISOString();
|
|
253
500
|
saveMeshConfig(config);
|
|
254
501
|
return node;
|
|
@@ -92,12 +92,18 @@ export function buildSessionReadStateKey(sessionId: string, providerSessionId?:
|
|
|
92
92
|
|
|
93
93
|
export function getSessionSeenAt(state: DaemonState, sessionId: string, providerSessionId?: string | null): number {
|
|
94
94
|
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
95
|
-
return state.sessionReads?.[providerKey] || state.sessionReads?.[sessionId] || 0;
|
|
95
|
+
return Math.max(state.sessionReads?.[providerKey] || 0, state.sessionReads?.[sessionId] || 0);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export function getSessionSeenMarker(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
99
99
|
const providerKey = buildSessionReadStateKey(sessionId, providerSessionId);
|
|
100
|
-
|
|
100
|
+
const providerSeenAt = state.sessionReads?.[providerKey] || 0;
|
|
101
|
+
const sessionSeenAt = state.sessionReads?.[sessionId] || 0;
|
|
102
|
+
const providerMarker = state.sessionReadMarkers?.[providerKey] || '';
|
|
103
|
+
const sessionMarker = state.sessionReadMarkers?.[sessionId] || '';
|
|
104
|
+
if (sessionSeenAt > providerSeenAt && sessionMarker) return sessionMarker;
|
|
105
|
+
if (providerSeenAt > sessionSeenAt && providerMarker) return providerMarker;
|
|
106
|
+
return providerMarker || sessionMarker;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export function getSessionNotificationDismissal(state: DaemonState, sessionId: string, providerSessionId?: string | null): string {
|
|
@@ -802,7 +802,16 @@ export async function handleCliSend(ctx: DevServerContext, req: http.IncomingMes
|
|
|
802
802
|
}
|
|
803
803
|
|
|
804
804
|
try {
|
|
805
|
-
|
|
805
|
+
if (target.category === 'cli') {
|
|
806
|
+
const bundle = getCliTargetBundle(ctx, type, instanceId);
|
|
807
|
+
if (!bundle) {
|
|
808
|
+
ctx.json(res, 404, { error: `No running CLI adapter found for: ${type || instanceId}` });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await bundle.adapter.sendMessage(text);
|
|
812
|
+
} else {
|
|
813
|
+
ctx.instanceManager!.sendEvent(target.instanceId, 'send_message', { text });
|
|
814
|
+
}
|
|
806
815
|
ctx.json(res, 200, { sent: true, type: target.type, instanceId: target.instanceId });
|
|
807
816
|
} catch (e: any) {
|
|
808
817
|
ctx.json(res, 500, { error: `Send failed: ${e.message}` });
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* Migrated from @adhdev/core — this is now the single source of truth.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
import { existsSync, statSync } from 'fs';
|
|
12
14
|
import { platform, homedir } from 'os';
|
|
13
15
|
import * as path from 'path';
|
|
14
16
|
import type { ProviderLoader } from '../providers/provider-loader.js';
|
|
@@ -73,25 +75,33 @@ function findCliCommand(command: string): string | null {
|
|
|
73
75
|
const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(candidate);
|
|
74
76
|
return existsSync(resolved) ? resolved : null;
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const isWin = platform() === 'win32';
|
|
79
|
+
const paths = (process.env.PATH || '').split(isWin ? ';' : ':');
|
|
80
|
+
const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
81
|
+
for (const p of paths) {
|
|
82
|
+
if (!p) continue;
|
|
83
|
+
for (const ext of exes) {
|
|
84
|
+
const fullPath = path.join(p, trimmed + ext);
|
|
85
|
+
try {
|
|
86
|
+
if (existsSync(fullPath)) {
|
|
87
|
+
const stat = statSync(fullPath);
|
|
88
|
+
if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
|
|
89
|
+
return fullPath;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch { }
|
|
93
|
+
}
|
|
84
94
|
}
|
|
95
|
+
return null;
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
function getIdeVersion(cliCommand: string): string | null {
|
|
98
|
+
async function getIdeVersion(cliCommand: string): Promise<string | null> {
|
|
88
99
|
try {
|
|
89
|
-
const
|
|
100
|
+
const { stdout } = await execAsync(`"${cliCommand}" --version`, {
|
|
90
101
|
encoding: 'utf-8',
|
|
91
102
|
timeout: 10000,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return result.split('\n')[0] || null;
|
|
103
|
+
});
|
|
104
|
+
return stdout.trim().split('\n')[0] || null;
|
|
95
105
|
} catch {
|
|
96
106
|
return null;
|
|
97
107
|
}
|
|
@@ -152,7 +162,7 @@ export async function detectIDEs(providerLoader?: ProviderLoader): Promise<IDEIn
|
|
|
152
162
|
const installed = os === 'darwin'
|
|
153
163
|
? !!(resolvedCli || appPath)
|
|
154
164
|
: !!resolvedCli;
|
|
155
|
-
const version = resolvedCli ? getIdeVersion(resolvedCli) : null;
|
|
165
|
+
const version = resolvedCli ? await getIdeVersion(resolvedCli) : null;
|
|
156
166
|
|
|
157
167
|
results.push({
|
|
158
168
|
id: def.id,
|
package/src/git/git-commands.ts
CHANGED
|
@@ -42,8 +42,12 @@ export interface GitLogResult extends GitRepoIdentity {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface GitCheckpointResult extends GitRepoIdentity {
|
|
45
|
-
commit
|
|
45
|
+
commit?: string;
|
|
46
46
|
message: string;
|
|
47
|
+
status?: 'created' | 'skipped';
|
|
48
|
+
skipped?: boolean;
|
|
49
|
+
noop?: boolean;
|
|
50
|
+
reason?: 'nothing_to_commit';
|
|
47
51
|
lastCheckedAt: number;
|
|
48
52
|
}
|
|
49
53
|
|
|
@@ -455,10 +459,17 @@ async function gitCheckpoint(
|
|
|
455
459
|
} catch (err: any) {
|
|
456
460
|
const output = (err?.stdout || '') + (err?.stderr || '');
|
|
457
461
|
if (/nothing to commit/i.test(output)) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
+
return {
|
|
463
|
+
workspace: repo.workspace,
|
|
464
|
+
repoRoot,
|
|
465
|
+
isGitRepo: true,
|
|
466
|
+
message: fullMsg,
|
|
467
|
+
status: 'skipped',
|
|
468
|
+
skipped: true,
|
|
469
|
+
noop: true,
|
|
470
|
+
reason: 'nothing_to_commit',
|
|
471
|
+
lastCheckedAt: Date.now(),
|
|
472
|
+
};
|
|
462
473
|
}
|
|
463
474
|
throw err;
|
|
464
475
|
}
|
|
@@ -469,6 +480,7 @@ async function gitCheckpoint(
|
|
|
469
480
|
isGitRepo: true,
|
|
470
481
|
commit: commitSha,
|
|
471
482
|
message: fullMsg,
|
|
483
|
+
status: 'created',
|
|
472
484
|
lastCheckedAt: Date.now(),
|
|
473
485
|
};
|
|
474
486
|
}
|