@adhdev/daemon-core 0.9.82-rc.261 → 0.9.82-rc.263
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/build-info.d.ts +37 -0
- package/dist/commands/router.d.ts +116 -0
- package/dist/git/git-status.d.ts +7 -0
- package/dist/git/git-types.d.ts +19 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +971 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +967 -69
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-active-work.d.ts +18 -0
- package/dist/mesh/mesh-fast-forward.d.ts +41 -1
- package/dist/mesh/mesh-ledger.d.ts +1 -1
- package/dist/mesh/mesh-runtime-store.d.ts +6 -0
- package/dist/mesh/mesh-work-queue.d.ts +6 -0
- package/package.json +1 -1
- package/src/build-info.ts +73 -0
- package/src/commands/router.ts +805 -9
- package/src/git/git-status.ts +73 -1
- package/src/git/git-types.ts +20 -0
- package/src/index.ts +5 -2
- package/src/mesh/mesh-active-work.ts +31 -0
- package/src/mesh/mesh-fast-forward.ts +418 -17
- package/src/mesh/mesh-ledger.ts +1 -0
- package/src/mesh/mesh-runtime-store.ts +19 -0
- package/src/mesh/mesh-work-queue.ts +13 -0
package/src/git/git-status.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { GitRepoStatus, GitSubmoduleStatus, GitUpstreamFreshness } from './git-types.js';
|
|
1
|
+
import type { DaemonBuildBehind, GitRepoStatus, GitSubmoduleStatus, GitUpstreamFreshness } from './git-types.js';
|
|
2
2
|
import { GitCommandError, resolveGitRepository, runGit } from './git-executor.js';
|
|
3
|
+
import { getDaemonBuildInfo, type DaemonBuildInfo } from '../build-info.js';
|
|
3
4
|
|
|
4
5
|
type ResolvedGitRepo = { workspace: string; repoRoot: string | null; isGitRepo: boolean };
|
|
5
6
|
|
|
@@ -14,6 +15,12 @@ export interface GitStatusOptions {
|
|
|
14
15
|
* Callers should opt into this only for convergence-critical surfaces.
|
|
15
16
|
*/
|
|
16
17
|
refreshUpstream?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Test/override seam for the daemon build stamp used by the stale-build
|
|
20
|
+
* detector. Production callers omit this so the real baked-in build commit
|
|
21
|
+
* (getDaemonBuildInfo) is used.
|
|
22
|
+
*/
|
|
23
|
+
daemonBuildInfo?: DaemonBuildInfo;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
interface GitUpstreamProbe {
|
|
@@ -54,6 +61,8 @@ export async function getGitRepoStatus(
|
|
|
54
61
|
|| stashCount > 0
|
|
55
62
|
|| submoduleDirty;
|
|
56
63
|
|
|
64
|
+
const daemonBuildBehind = await detectDaemonBuildBehind(repo, submodules, options);
|
|
65
|
+
|
|
57
66
|
return {
|
|
58
67
|
workspace: repo.workspace,
|
|
59
68
|
repoRoot: repo.repoRoot,
|
|
@@ -78,6 +87,7 @@ export async function getGitRepoStatus(
|
|
|
78
87
|
stashCount,
|
|
79
88
|
lastCheckedAt,
|
|
80
89
|
submodules,
|
|
90
|
+
...(daemonBuildBehind ? { daemonBuildBehind } : {}),
|
|
81
91
|
};
|
|
82
92
|
} catch (error) {
|
|
83
93
|
if (error instanceof GitCommandError) {
|
|
@@ -91,6 +101,68 @@ export async function getGitRepoStatus(
|
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Detect whether the running daemon's build commit is a STRICT ancestor of this
|
|
106
|
+
* workspace's HEAD (root) or any of its submodules' HEAD. This surfaces the
|
|
107
|
+
* "merged a fix to main but the live daemon still ships the old bundle" gap:
|
|
108
|
+
* once the fix is committed, the workspace HEAD advances past the daemon's
|
|
109
|
+
* baked-in build commit, but the daemon keeps the old behavior until it is
|
|
110
|
+
* rebuilt/redeployed and restarted.
|
|
111
|
+
*
|
|
112
|
+
* Conservative by construction — returns undefined unless ancestry is provable:
|
|
113
|
+
* - build commit unknown → undefined
|
|
114
|
+
* - build commit not an object in this repo/submodule (different repo) → skip
|
|
115
|
+
* - build commit === HEAD (daemon is current) → undefined
|
|
116
|
+
* - build commit NOT an ancestor of HEAD (daemon ahead / diverged) → undefined
|
|
117
|
+
* Any git error is swallowed (no warning) so a flaky probe never over-warns.
|
|
118
|
+
*/
|
|
119
|
+
async function detectDaemonBuildBehind(
|
|
120
|
+
repo: ResolvedGitRepo,
|
|
121
|
+
submodules: GitSubmoduleStatus[] | undefined,
|
|
122
|
+
options: GitStatusOptions,
|
|
123
|
+
): Promise<DaemonBuildBehind | undefined> {
|
|
124
|
+
const build = options.daemonBuildInfo ?? getDaemonBuildInfo();
|
|
125
|
+
if (!build.commit || build.commit === 'unknown') return undefined;
|
|
126
|
+
|
|
127
|
+
// Check the root repo first, then each submodule. The daemon build commit is
|
|
128
|
+
// baked from the daemon-core (oss submodule) HEAD, so on an adhdev
|
|
129
|
+
// superproject worktree the match is expected on the `oss` submodule, not the
|
|
130
|
+
// root — checking both keeps the helper repo-agnostic.
|
|
131
|
+
const scopes: Array<{ scope: string; repoPath: string }> = [
|
|
132
|
+
{ scope: 'root', repoPath: repo.repoRoot || repo.workspace },
|
|
133
|
+
];
|
|
134
|
+
for (const sub of submodules || []) {
|
|
135
|
+
if (sub.repoPath && !sub.error) scopes.push({ scope: sub.path, repoPath: sub.repoPath });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const { scope, repoPath } of scopes) {
|
|
139
|
+
try {
|
|
140
|
+
// Build commit must be a real object in THIS repo, else it's a different repo.
|
|
141
|
+
await runGit(repoPath, ['cat-file', '-e', `${build.commit}^{commit}`], options);
|
|
142
|
+
const headResult = await runGit(repoPath, ['rev-parse', 'HEAD'], options);
|
|
143
|
+
const head = headResult.stdout.trim();
|
|
144
|
+
if (!head || head === build.commit) continue;
|
|
145
|
+
// Strict ancestor: build commit is reachable from HEAD but is not HEAD.
|
|
146
|
+
await runGit(repoPath, ['merge-base', '--is-ancestor', build.commit, 'HEAD'], options);
|
|
147
|
+
// No throw → build commit IS an ancestor of HEAD → daemon is behind.
|
|
148
|
+
return {
|
|
149
|
+
buildCommit: build.commit,
|
|
150
|
+
buildCommitShort: build.commitShort,
|
|
151
|
+
head,
|
|
152
|
+
scope,
|
|
153
|
+
warning:
|
|
154
|
+
`Live daemon was built from ${build.commitShort} which is behind ${scope === 'root' ? 'workspace' : scope} HEAD ${head.slice(0, 7)}. ` +
|
|
155
|
+
`Merged code is NOT live until the daemon is rebuilt/redeployed and restarted — a local dist rebuild alone does not update a cloud daemon.`,
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
// cat-file / merge-base non-zero exit (commit absent or not an ancestor)
|
|
159
|
+
// or any git error → not a provable staleness for this scope; try next.
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
94
166
|
interface ParsedPorcelainStatus {
|
|
95
167
|
branch: string | null;
|
|
96
168
|
upstream: string | null;
|
package/src/git/git-types.ts
CHANGED
|
@@ -68,10 +68,30 @@ export interface GitRepoStatus extends GitRepoIdentity {
|
|
|
68
68
|
lastCheckedAt: number;
|
|
69
69
|
/** Submodule statuses when auto-discover is enabled */
|
|
70
70
|
submodules?: GitSubmoduleStatus[];
|
|
71
|
+
/**
|
|
72
|
+
* Set when the running daemon's build commit is a STRICT ancestor of this
|
|
73
|
+
* repo's HEAD (or a submodule HEAD) — i.e. the live daemon predates committed
|
|
74
|
+
* code in this workspace and is awaiting a deploy/restart to catch up.
|
|
75
|
+
* Omitted entirely when no staleness is provable (unknown build, commit not
|
|
76
|
+
* present in repo, or build commit == HEAD) to avoid over-warning.
|
|
77
|
+
*/
|
|
78
|
+
daemonBuildBehind?: DaemonBuildBehind;
|
|
71
79
|
error?: string;
|
|
72
80
|
reason?: GitFailureReason;
|
|
73
81
|
}
|
|
74
82
|
|
|
83
|
+
export interface DaemonBuildBehind {
|
|
84
|
+
/** Full build commit baked into the running daemon. */
|
|
85
|
+
buildCommit: string;
|
|
86
|
+
/** Short build commit. */
|
|
87
|
+
buildCommitShort: string;
|
|
88
|
+
/** HEAD commit the build commit is behind (repo or submodule). */
|
|
89
|
+
head: string;
|
|
90
|
+
/** Where the comparison matched: 'root' or the submodule path. */
|
|
91
|
+
scope: string;
|
|
92
|
+
warning: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
75
95
|
export type GitFileChangeStatus =
|
|
76
96
|
| 'added'
|
|
77
97
|
| 'modified'
|
package/src/index.ts
CHANGED
|
@@ -217,9 +217,10 @@ export { buildMeshLedgerReconciliationEvidence, buildMeshLedgerReplicaEvidence }
|
|
|
217
217
|
export type { AnyLedgerSlice, MeshLedgerReconciliationEvidence, MeshLedgerReplicaEvidence, MeshLedgerReplicaStatus } from './mesh/mesh-ledger-reconciliation.js';
|
|
218
218
|
|
|
219
219
|
// ── Mesh Work Queue (GUPP) ──
|
|
220
|
-
export { enqueueTask, recordDirectDispatchTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest, buildMeshNodeCapabilityTags, nodeSatisfiesRequiredTags, normalizeMeshCapabilityTags, insertDirectDispatch, getActiveDirectDispatches, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, markStaleDirectDispatches, recordMeshToolCall, assertNoDependencyCycle, hasPendingDependents, describeTaskDependencyState } from './mesh/mesh-work-queue.js';
|
|
220
|
+
export { enqueueTask, recordDirectDispatchTask, getQueue, claimNextTask, updateTaskStatus, updateSessionTaskStatus, cancelTask, requeueTask, getMeshQueueStats, getMeshQueueRevision, normalizeMeshTaskMode, validateMeshTaskModeRequest, buildMeshNodeCapabilityTags, nodeSatisfiesRequiredTags, normalizeMeshCapabilityTags, insertDirectDispatch, getActiveDirectDispatches, updateDirectDispatchStatus, cleanupTerminalDirectDispatches, markStaleDirectDispatches, deleteDirectDispatchesByTaskId, recordMeshToolCall, assertNoDependencyCycle, hasPendingDependents, describeTaskDependencyState } from './mesh/mesh-work-queue.js';
|
|
221
221
|
export type { MeshWorkQueueEntry, MeshTaskStatus, MeshTaskMode, MeshWorkQueueStats, MeshQueueMutationOptions, MeshTaskModeValidationResult, DirectDispatchRecord, MeshToolCallRateResult } from './mesh/mesh-work-queue.js';
|
|
222
|
-
export { buildCompactStaleDirectWorkSummary, buildMeshActiveWork, buildMeshActiveWorkSummary } from './mesh/mesh-active-work.js';
|
|
222
|
+
export { buildCompactStaleDirectWorkSummary, buildMeshActiveWork, buildMeshActiveWorkSummary, classifyStaleDirectForPrune, PRUNABLE_ORPHAN_STALE_REASONS } from './mesh/mesh-active-work.js';
|
|
223
|
+
export type { StaleDirectPruneClassification } from './mesh/mesh-active-work.js';
|
|
223
224
|
export type { MeshActiveWorkRecord, MeshActiveWorkStatus, MeshActiveWorkSummary, MeshActiveWorkSource, MeshStaleDirectWorkSummary } from './mesh/mesh-active-work.js';
|
|
224
225
|
export { buildMeshAsyncRefineJobs, summarizeMeshAsyncRefineJobs, STALE_TERMINAL_REFINE_WINDOW_MS, RECENT_TERMINAL_REFINE_CAP } from './mesh/mesh-refine-status.js';
|
|
225
226
|
export type { MeshAsyncRefineJobStatus, MeshAsyncRefineJobSummary, MeshAsyncRefineJobsSummary } from './mesh/mesh-refine-status.js';
|
|
@@ -306,6 +307,8 @@ export type {
|
|
|
306
307
|
export { DaemonStatusReporter } from './status/reporter.js';
|
|
307
308
|
export { buildSessionEntries, findCdpManager, hasCdpManager, isCdpConnected } from './status/builders.js';
|
|
308
309
|
export { buildStatusSnapshot, buildMachineInfo } from './status/snapshot.js';
|
|
310
|
+
export { getDaemonBuildInfo } from './build-info.js';
|
|
311
|
+
export type { DaemonBuildInfo } from './build-info.js';
|
|
309
312
|
export { normalizeManagedStatus, isManagedStatusWorking, isManagedStatusWaiting, normalizeActiveChatData } from './status/normalize.js';
|
|
310
313
|
export type { ManagedStatus } from './status/normalize.js';
|
|
311
314
|
export type { StatusSnapshotOptions, StatusSnapshot } from './status/snapshot.js';
|
|
@@ -408,6 +408,37 @@ export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeW
|
|
|
408
408
|
return { activeWork: records, staleDirectWork, staleDirectWorkNote, terminalDirectWork, summary };
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
/**
|
|
412
|
+
* staleReason strings (produced by sessionStatusFromNodes above) that indicate the original
|
|
413
|
+
* node/session is GONE from the live mesh — i.e. the staleDirect record is an orphaned ledger
|
|
414
|
+
* artifact, not active or recoverable work. These are the only reasons safe to prune from the
|
|
415
|
+
* active staleDirect surface. The "no provider acknowledgement" reason is deliberately excluded:
|
|
416
|
+
* those entries have a still-live node/session (staleDispatchUnacknowledged) and represent
|
|
417
|
+
* recoverable dispatch failures, never orphans.
|
|
418
|
+
*/
|
|
419
|
+
export const PRUNABLE_ORPHAN_STALE_REASONS: ReadonlySet<string> = new Set([
|
|
420
|
+
'direct task node is no longer in the live mesh',
|
|
421
|
+
'direct task session is not present in live session records',
|
|
422
|
+
'direct task has no node id',
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
export type StaleDirectPruneClassification = 'prunable_orphan' | 'prunable_terminal' | 'preserve_unacknowledged' | 'preserve_active';
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Classify a direct-work record for the staleDirect prune path. Pure function — the prune tool
|
|
429
|
+
* uses this so the safety rules (never touch active work or recoverable unacknowledged dispatches)
|
|
430
|
+
* live next to the staleReason producers and are independently testable.
|
|
431
|
+
*/
|
|
432
|
+
export function classifyStaleDirectForPrune(
|
|
433
|
+
record: Pick<MeshActiveWorkRecord, 'staleReason' | 'staleDispatchUnacknowledged' | 'terminal'>,
|
|
434
|
+
opts: { includeTerminal?: boolean } = {},
|
|
435
|
+
): StaleDirectPruneClassification {
|
|
436
|
+
if (record.staleDispatchUnacknowledged === true) return 'preserve_unacknowledged';
|
|
437
|
+
if (record.terminal === true) return opts.includeTerminal ? 'prunable_terminal' : 'preserve_active';
|
|
438
|
+
if (record.staleReason && PRUNABLE_ORPHAN_STALE_REASONS.has(record.staleReason)) return 'prunable_orphan';
|
|
439
|
+
return 'preserve_active';
|
|
440
|
+
}
|
|
441
|
+
|
|
411
442
|
export function buildCompactStaleDirectWorkSummary(
|
|
412
443
|
staleDirectWork: MeshActiveWorkRecord[],
|
|
413
444
|
opts: { sampleLimit?: number; detailHint?: string; note?: string } = {},
|