@adhdev/mesh-shared 1.0.82-rc.3 → 1.0.82-rc.30
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/assistant-protocol.d.ts +140 -6
- package/dist/index.d.ts +6 -0
- package/dist/index.js +529 -110
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +500 -110
- package/dist/index.mjs.map +1 -1
- package/dist/launch-defaults.d.ts +194 -0
- package/dist/mesh-tool-names.d.ts +2 -2
- package/dist/mesh-vocabulary.d.ts +13 -4
- package/dist/node-facts.d.ts +41 -1
- package/dist/outbound-message.d.ts +7 -0
- package/dist/path-ownership.d.ts +1 -1
- package/dist/protocol/daemon-server.d.ts +9 -0
- package/dist/protocol/dashboard-daemon-p2p.d.ts +1 -1
- package/dist/protocol/dashboard-server.d.ts +10 -0
- package/dist/provider-approval.d.ts +36 -0
- package/dist/provider-question.d.ts +19 -0
- package/dist/quota-freshness.d.ts +65 -0
- package/dist/role-launch.d.ts +11 -0
- package/dist/session-launch.d.ts +3 -22
- package/dist/slot-proposal.d.ts +14 -0
- package/dist/task-source-phase.d.ts +75 -0
- package/dist/turn-evidence.d.ts +24 -12
- package/dist/turn-ipc-mission.d.ts +4 -0
- package/dist/turn-ipc-queue.d.ts +19 -0
- package/dist/turn-ipc.d.ts +2 -2
- package/dist/types.d.ts +1 -0
- package/dist/worker-protocol.d.ts +1 -1
- package/package.json +1 -1
- package/src/assistant-protocol.ts +247 -6
- package/src/daemon-normalize.ts +7 -5
- package/src/index.ts +7 -0
- package/src/keyed-doc-delta.ts +9 -1
- package/src/launch-defaults.ts +168 -0
- package/src/mesh-tool-names.ts +5 -0
- package/src/mesh-vocabulary.ts +12 -2
- package/src/node-facts.ts +36 -1
- package/src/outbound-message.ts +17 -0
- package/src/path-ownership.ts +4 -6
- package/src/protocol/daemon-server.ts +7 -2
- package/src/protocol/dashboard-server.ts +8 -2
- package/src/provider-approval.ts +59 -0
- package/src/provider-question.ts +31 -0
- package/src/quota-freshness.ts +139 -0
- package/src/role-launch.ts +51 -0
- package/src/session-launch.ts +8 -1
- package/src/slot-proposal.ts +61 -2
- package/src/task-source-phase.ts +61 -0
- package/src/turn-evidence.ts +22 -19
- package/src/turn-ipc-mission.ts +8 -2
- package/src/turn-ipc-queue.ts +29 -1
- package/src/turn-ipc-turn.ts +1 -1
- package/src/turn-ipc.ts +1 -0
- package/src/types.ts +1 -0
- package/src/worker-protocol.ts +4 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { OwnedPathEntry } from './path-ownership';
|
|
2
|
+
export type MeshTaskPhase = 'source' | 'integration';
|
|
3
|
+
/** Host-produced evidence. Never accepted from MCP/peer-supplied paths or branch labels. */
|
|
4
|
+
export interface PhysicalOwnershipScope {
|
|
5
|
+
version: 1;
|
|
6
|
+
repoIdentity: string;
|
|
7
|
+
machineId: string;
|
|
8
|
+
nodeId: string;
|
|
9
|
+
worktree: string;
|
|
10
|
+
worktreeIdentity: string;
|
|
11
|
+
gitDir: string;
|
|
12
|
+
commonGitDir: string;
|
|
13
|
+
phase: MeshTaskPhase;
|
|
14
|
+
gitResources: {
|
|
15
|
+
index: string;
|
|
16
|
+
refs: string;
|
|
17
|
+
hooks: string;
|
|
18
|
+
config: string;
|
|
19
|
+
};
|
|
20
|
+
resources: Array<{
|
|
21
|
+
path: string;
|
|
22
|
+
identity?: string;
|
|
23
|
+
subtree: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
declaration: readonly OwnedPathEntry[];
|
|
26
|
+
}
|
|
27
|
+
/** A source packet is an input to integration, never release/feature acceptance. */
|
|
28
|
+
export interface MeshSourceCandidate {
|
|
29
|
+
version: 1;
|
|
30
|
+
readiness: 'uncommitted' | 'verified_source_candidate';
|
|
31
|
+
taskId: string;
|
|
32
|
+
missionId: string;
|
|
33
|
+
revision: number;
|
|
34
|
+
repoIdentity: string;
|
|
35
|
+
machineId: string;
|
|
36
|
+
canonicalWorktree: string;
|
|
37
|
+
rootBaseSha: string;
|
|
38
|
+
ossBaseSha: string;
|
|
39
|
+
rootCandidateSha?: string;
|
|
40
|
+
ossCandidateSha?: string;
|
|
41
|
+
patchSha256: string;
|
|
42
|
+
manifestSha256: string;
|
|
43
|
+
touchedPaths: string[];
|
|
44
|
+
tests: Array<{
|
|
45
|
+
command: string;
|
|
46
|
+
exit: number;
|
|
47
|
+
logSha256: string;
|
|
48
|
+
}>;
|
|
49
|
+
settledCommands: boolean;
|
|
50
|
+
integrationTaskId: string;
|
|
51
|
+
logicalOverlapTaskIds: string[];
|
|
52
|
+
}
|
|
53
|
+
/** Product B consumes one frozen batch; normal hooks/parity/gates remain required. */
|
|
54
|
+
export interface MeshIntegrationBatchContract {
|
|
55
|
+
version: 1;
|
|
56
|
+
batchId: string;
|
|
57
|
+
candidates: MeshSourceCandidate[];
|
|
58
|
+
integrationOrder: string[];
|
|
59
|
+
approvedRootSha: string;
|
|
60
|
+
approvedOssSha: string;
|
|
61
|
+
bundleInvocationCount: 1;
|
|
62
|
+
requiredGates: string[];
|
|
63
|
+
normalHooksRequired: true;
|
|
64
|
+
independentReviewRequired: true;
|
|
65
|
+
ownerLandingApprovalRequired: true;
|
|
66
|
+
}
|
|
67
|
+
/** These only rank legal targets; targetNodeId/SessionId and requiredTags remain hard. */
|
|
68
|
+
export interface MeshRoutingPreferences {
|
|
69
|
+
nodeIds?: string[];
|
|
70
|
+
providers?: string[];
|
|
71
|
+
}
|
|
72
|
+
export interface MeshReviewConstraint {
|
|
73
|
+
sourceTaskId: string;
|
|
74
|
+
differentProvider: true;
|
|
75
|
+
}
|
package/dist/turn-evidence.d.ts
CHANGED
|
@@ -58,20 +58,20 @@ export declare const SESSION_REBOUND_REASONS: readonly ["restart", "live_holder"
|
|
|
58
58
|
export type SessionReboundReason = typeof SESSION_REBOUND_REASONS[number];
|
|
59
59
|
export declare const SUSPENSION_MODALS: readonly ["approval", "choice"];
|
|
60
60
|
export type SuspensionModal = typeof SUSPENSION_MODALS[number];
|
|
61
|
-
export declare const SUSPENSION_RESOLUTIONS: readonly ["approved", "rejected", "answered"];
|
|
61
|
+
export declare const SUSPENSION_RESOLUTIONS: readonly ["approved", "rejected", "answered", "dismissed"];
|
|
62
62
|
export type SuspensionResolution = typeof SUSPENSION_RESOLUTIONS[number];
|
|
63
|
-
export declare const SUSPENSION_RESOLUTION_VIAS: readonly ["modal_button", "auto_approve", "prompt_answer"];
|
|
63
|
+
export declare const SUSPENSION_RESOLUTION_VIAS: readonly ["modal_button", "auto_approve", "prompt_answer", "modal_disappeared"];
|
|
64
64
|
export type SuspensionResolutionVia = typeof SUSPENSION_RESOLUTION_VIAS[number];
|
|
65
65
|
export declare const TURN_END_STRENGTHS: readonly ["genuine", "weak"];
|
|
66
66
|
export type TurnEndStrength = typeof TURN_END_STRENGTHS[number];
|
|
67
67
|
/** Why a provider-side weak end was released (was free text `blockReason`). */
|
|
68
68
|
export declare const TURN_END_BLOCK_REASONS: readonly ["missing_final_assistant", "finalization_timeout", "terminal_block_hard_cap", "decoupled_completion"];
|
|
69
69
|
export type TurnEndBlockReason = typeof TURN_END_BLOCK_REASONS[number];
|
|
70
|
-
export declare const NATIVE_TURN_OUTCOMES: readonly ["completed", "aborted"];
|
|
70
|
+
export declare const NATIVE_TURN_OUTCOMES: readonly ["completed", "aborted", "failed"];
|
|
71
71
|
export type NativeTurnOutcome = typeof NATIVE_TURN_OUTCOMES[number];
|
|
72
72
|
export declare const LIVENESS_RESULTS: readonly ["alive", "unknown", "read_failed", "dead"];
|
|
73
73
|
export type LivenessResult = typeof LIVENESS_RESULTS[number];
|
|
74
|
-
export declare const PROVIDER_FAILURES: readonly ["auth_failed", "billing_failed"];
|
|
74
|
+
export declare const PROVIDER_FAILURES: readonly ["auth_failed", "billing_failed", "usage_limit_exceeded", "server_overloaded", "provider_error"];
|
|
75
75
|
export type ProviderFailure = typeof PROVIDER_FAILURES[number];
|
|
76
76
|
/**
|
|
77
77
|
* `daemon_restart` (wiring-unification follow-up, design §5): boot-time
|
|
@@ -99,14 +99,14 @@ export type NoProgressObservedStatus = SessionStatus | 'unknown';
|
|
|
99
99
|
* Reasons a turn hold exists. Holds are attempt-scoped only (C2: delivery-side
|
|
100
100
|
* waits are consumer deferral on the `turn.deliver` cursor, not hold rows).
|
|
101
101
|
*/
|
|
102
|
-
export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report", "await_end"];
|
|
102
|
+
export declare const HOLD_REASONS: readonly ["await_delivery", "await_consume", "await_turn", "liveness", "hard_ceiling", "live_pending", "transcript_quiet", "weak_candidate", "suspension_before_consumed", "await_report", "await_end", "report_correction"];
|
|
103
103
|
export type HoldReason = typeof HOLD_REASONS[number];
|
|
104
|
-
export declare const TURN_OUTCOMES: readonly ["completed", "failed", "cancelled"];
|
|
104
|
+
export declare const TURN_OUTCOMES: readonly ["completed", "blocked", "failed", "cancelled"];
|
|
105
105
|
export type TurnOutcome = typeof TURN_OUTCOMES[number];
|
|
106
106
|
export declare const COMMIT_STRENGTHS: readonly ["genuine", "weak", "tool_report", "operator"];
|
|
107
107
|
export type CommitStrength = typeof COMMIT_STRENGTHS[number];
|
|
108
108
|
/** Every terminal or reclaim reason the ledger writes. Closed: no free-text reasons. */
|
|
109
|
-
export declare const TURN_REASONS: readonly ["turn_end", "weak_end_confirmed", "transcript_final", "worker_reported", "finalization_timeout_no_response", "session_error", "provider_auth_failed", "provider_billing_failed", "operator_cancel", "intentional_cleanup", "task_removed", "mission_abandoned", "superseded", "operator_update", "refine_terminal", "validation_terminal", "hard_ceiling", "reclaim_budget_exhausted", "hollow_max_retries", "migration_orphan", "dispatch_refused_session_exited", "dispatch_refused_no_target", "dispatch_refused_unsupported_input", "dispatch_failed", "session_exit", "session_exit_before_turn", "session_dead", "hollow_completion", "assigned_stranded_dispatch_unconfirmed", "delivered_not_consumed_redrive", "delivered_no_turn_deadline"];
|
|
109
|
+
export declare const TURN_REASONS: readonly ["turn_end", "weak_end_confirmed", "transcript_final", "worker_reported", "finalization_timeout_no_response", "weak_end_liveness_unconfirmed", "weak_end_probe_unobserved", "report_correction_expired", "session_error", "provider_auth_failed", "provider_billing_failed", "provider_usage_limit_exceeded", "provider_server_overloaded", "provider_error", "operator_cancel", "intentional_cleanup", "task_removed", "mission_abandoned", "superseded", "operator_update", "refine_terminal", "validation_terminal", "hard_ceiling", "reclaim_budget_exhausted", "hollow_max_retries", "migration_orphan", "dispatch_refused_session_exited", "dispatch_refused_no_target", "dispatch_refused_unsupported_input", "dispatch_failed", "session_exit", "session_exit_before_turn", "session_dead", "hollow_completion", "assigned_stranded_dispatch_unconfirmed", "delivered_not_consumed_redrive", "delivered_no_turn_deadline"];
|
|
110
110
|
export type TurnReason = typeof TURN_REASONS[number];
|
|
111
111
|
/**
|
|
112
112
|
* What a `turn.notify` entry tells a coordinator.
|
|
@@ -118,7 +118,7 @@ export type TurnReason = typeof TURN_REASONS[number];
|
|
|
118
118
|
* (refine terminal, worktree bootstrap, …) whose text lives only in the
|
|
119
119
|
* owner's local `turn_events.payload_json` and is rendered at deliver time.
|
|
120
120
|
*/
|
|
121
|
-
export declare const NOTIFY_KINDS: readonly ["completed", "failed", "cancelled", "stopped", "approval", "choice", "approval_resolved", "candidate", "no_progress", "progress", "late_completion", "mesh_event"];
|
|
121
|
+
export declare const NOTIFY_KINDS: readonly ["completed", "blocked", "failed", "cancelled", "stopped", "approval", "choice", "approval_resolved", "candidate", "no_progress", "progress", "late_completion", "mesh_event"];
|
|
122
122
|
export type NotifyKind = typeof NOTIFY_KINDS[number];
|
|
123
123
|
export interface TurnAttemptRef {
|
|
124
124
|
attemptId: string;
|
|
@@ -149,6 +149,9 @@ export interface EvidenceCoordinatorIdentity {
|
|
|
149
149
|
export interface NativeTurnMarkerRef {
|
|
150
150
|
outcome: NativeTurnOutcome;
|
|
151
151
|
turnId?: string;
|
|
152
|
+
receivedAt?: number;
|
|
153
|
+
startedAt?: number;
|
|
154
|
+
errorAt?: number;
|
|
152
155
|
}
|
|
153
156
|
export interface EvidenceEnvelope {
|
|
154
157
|
/** Stable id of this observation; dedupe key across republish/replication. */
|
|
@@ -220,10 +223,13 @@ export type TurnEvidenceBody = {
|
|
|
220
223
|
kind: 'suspension';
|
|
221
224
|
modal: SuspensionModal;
|
|
222
225
|
modalKey?: string;
|
|
226
|
+
providerSetup?: boolean;
|
|
227
|
+
clockUnknown?: boolean;
|
|
223
228
|
} | {
|
|
224
229
|
kind: 'suspension_resolved';
|
|
225
230
|
resolution: SuspensionResolution;
|
|
226
231
|
via: SuspensionResolutionVia;
|
|
232
|
+
modalKey?: string;
|
|
227
233
|
} | {
|
|
228
234
|
kind: 'turn_end';
|
|
229
235
|
strength: TurnEndStrength;
|
|
@@ -233,6 +239,7 @@ export type TurnEvidenceBody = {
|
|
|
233
239
|
blockReason?: TurnEndBlockReason;
|
|
234
240
|
releasedByHardCap?: boolean;
|
|
235
241
|
nativeOutcome?: NativeTurnOutcome;
|
|
242
|
+
nativeMarker?: NativeTurnMarkerRef;
|
|
236
243
|
live?: LiveTurnPending;
|
|
237
244
|
/**
|
|
238
245
|
* Stamped (true only) by the WORKER's daemon when the session holds a live
|
|
@@ -268,6 +275,7 @@ export type TurnEvidenceBody = {
|
|
|
268
275
|
} | {
|
|
269
276
|
kind: 'liveness';
|
|
270
277
|
result: LivenessResult;
|
|
278
|
+
probeStartedAtMs?: number;
|
|
271
279
|
} | {
|
|
272
280
|
kind: 'process_exit';
|
|
273
281
|
exitCode: number | null;
|
|
@@ -288,6 +296,9 @@ export type TurnEvidenceBody = {
|
|
|
288
296
|
hasHandoffNotes: boolean;
|
|
289
297
|
branchState?: WorkerBranchState;
|
|
290
298
|
touchedFileCount?: number;
|
|
299
|
+
} | {
|
|
300
|
+
kind: 'worker_report_correction_requested';
|
|
301
|
+
deliveryId?: string;
|
|
291
302
|
} | {
|
|
292
303
|
kind: 'worker_progress';
|
|
293
304
|
note?: SummaryRef;
|
|
@@ -371,14 +382,15 @@ export declare const TURN_EVIDENCE_FIELD_SPECS: TurnEvidenceFieldSpecs;
|
|
|
371
382
|
export declare const TURN_EVIDENCE_KINDS: readonly TurnEvidenceKind[];
|
|
372
383
|
/** Evidence kinds that, on their own, can end a turn (the ones R18/R19/R27 reason about). */
|
|
373
384
|
export declare const TERMINAL_CLASS_EVIDENCE_KINDS: readonly ["turn_end", "transcript_final", "no_progress", "worker_report", "session_error", "process_exit", "cancel", "operator_status"];
|
|
374
|
-
export declare const isTurnEvidenceKind: (value: unknown) => value is "no_progress" | "delivered" | "liveness" | "turn_end" | "transcript_final" | "session_error" | "dispatch_failed" | "dispatch_accepted" | "delivery_refused" | "duplicate_dispatch_refusal" | "session_rebound" | "turn_started" | "suspension" | "suspension_resolved" | "transcript_activity" | "process_exit" | "worker_report" | "worker_progress" | "git_side_effect" | "cancel" | "operator_status" | "coordinator_ack" | "hold_expired";
|
|
375
|
-
export declare const isTurnReason: (value: unknown) => value is "intentional_cleanup" | "operator_cancel" | "task_removed" | "mission_abandoned" | "superseded" | "operator_update" | "refine_terminal" | "validation_terminal" | "hard_ceiling" | "turn_end" | "weak_end_confirmed" | "transcript_final" | "worker_reported" | "finalization_timeout_no_response" | "session_error" | "provider_auth_failed" | "provider_billing_failed" | "reclaim_budget_exhausted" | "hollow_max_retries" | "migration_orphan" | "dispatch_refused_session_exited" | "dispatch_refused_no_target" | "dispatch_refused_unsupported_input" | "dispatch_failed" | "session_exit" | "session_exit_before_turn" | "session_dead" | "hollow_completion" | "assigned_stranded_dispatch_unconfirmed" | "delivered_not_consumed_redrive" | "delivered_no_turn_deadline";
|
|
376
|
-
export declare const isNotifyKind: (value: unknown) => value is "stopped" | "failed" | "no_progress" | "completed" | "cancelled" | "approval" | "choice" | "approval_resolved" | "candidate" | "progress" | "late_completion" | "mesh_event";
|
|
377
|
-
export declare const isTurnOutcome: (value: unknown) => value is "failed" | "completed" | "cancelled";
|
|
385
|
+
export declare const isTurnEvidenceKind: (value: unknown) => value is "no_progress" | "delivered" | "liveness" | "turn_end" | "transcript_final" | "session_error" | "dispatch_failed" | "dispatch_accepted" | "delivery_refused" | "duplicate_dispatch_refusal" | "session_rebound" | "turn_started" | "suspension" | "suspension_resolved" | "transcript_activity" | "process_exit" | "worker_report" | "worker_report_correction_requested" | "worker_progress" | "git_side_effect" | "cancel" | "operator_status" | "coordinator_ack" | "hold_expired";
|
|
386
|
+
export declare const isTurnReason: (value: unknown) => value is "provider_error" | "intentional_cleanup" | "operator_cancel" | "task_removed" | "mission_abandoned" | "superseded" | "operator_update" | "refine_terminal" | "validation_terminal" | "hard_ceiling" | "turn_end" | "weak_end_confirmed" | "transcript_final" | "worker_reported" | "finalization_timeout_no_response" | "weak_end_liveness_unconfirmed" | "weak_end_probe_unobserved" | "report_correction_expired" | "session_error" | "provider_auth_failed" | "provider_billing_failed" | "provider_usage_limit_exceeded" | "provider_server_overloaded" | "reclaim_budget_exhausted" | "hollow_max_retries" | "migration_orphan" | "dispatch_refused_session_exited" | "dispatch_refused_no_target" | "dispatch_refused_unsupported_input" | "dispatch_failed" | "session_exit" | "session_exit_before_turn" | "session_dead" | "hollow_completion" | "assigned_stranded_dispatch_unconfirmed" | "delivered_not_consumed_redrive" | "delivered_no_turn_deadline";
|
|
387
|
+
export declare const isNotifyKind: (value: unknown) => value is "stopped" | "failed" | "blocked" | "no_progress" | "completed" | "cancelled" | "approval" | "choice" | "approval_resolved" | "candidate" | "progress" | "late_completion" | "mesh_event";
|
|
388
|
+
export declare const isTurnOutcome: (value: unknown) => value is "failed" | "blocked" | "completed" | "cancelled";
|
|
378
389
|
export declare const isCommitStrength: (value: unknown) => value is "operator" | "genuine" | "weak" | "tool_report";
|
|
379
390
|
export declare function isEvidenceIdentifier(value: unknown): value is string;
|
|
380
391
|
export declare function isSummaryRef(value: unknown): value is SummaryRef;
|
|
381
392
|
export declare function isTurnAttemptRef(value: unknown): value is TurnAttemptRef;
|
|
393
|
+
export declare function isNativeMarkerRef(value: unknown): value is NativeTurnMarkerRef;
|
|
382
394
|
/**
|
|
383
395
|
* Strict structural guard: known kind, every declared field of the right class,
|
|
384
396
|
* and NO undeclared field (an unknown key is how free text would sneak in).
|
|
@@ -89,6 +89,8 @@ export interface MeshMissionTaskAggregateWire {
|
|
|
89
89
|
lastActivityAt: string | null;
|
|
90
90
|
}
|
|
91
91
|
export interface MeshMissionStatsWire {
|
|
92
|
+
/** Present on peers that project proven blocked outcomes separately. */
|
|
93
|
+
blocked?: number;
|
|
92
94
|
missionId: string;
|
|
93
95
|
taskCount: number;
|
|
94
96
|
completed: number;
|
|
@@ -231,6 +233,8 @@ export interface LedgerQueryEntryWire {
|
|
|
231
233
|
payload: Record<string, unknown>;
|
|
232
234
|
}
|
|
233
235
|
export interface LedgerQuerySummaryWire {
|
|
236
|
+
/** Absent on older peers; never folded into failed by the decoder. */
|
|
237
|
+
taskBlocked?: number;
|
|
234
238
|
meshId: string;
|
|
235
239
|
totalEntries: number;
|
|
236
240
|
taskDispatched: number;
|
package/dist/turn-ipc-queue.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface QueueQueryResponse {
|
|
|
65
65
|
counts?: Record<string, number>;
|
|
66
66
|
/** `withCounts` + `historicalOlderThanMs`: terminal rows older than that age. */
|
|
67
67
|
oldHistoricalCount?: number;
|
|
68
|
+
/** Included in counts.blocked, derived from proven worker reports; stored failed rows stay unchanged. */
|
|
69
|
+
legacyReportedBlockedCount?: number;
|
|
68
70
|
/** `withDependencyHeads`. */
|
|
69
71
|
dependencyHeads?: readonly QueueDependencyHeadWire[];
|
|
70
72
|
}
|
|
@@ -194,3 +196,20 @@ export interface RecoveryContextQueryResponse {
|
|
|
194
196
|
}
|
|
195
197
|
export declare function decodeRecoveryContextQueryRequest(value: unknown): RecoveryContextQueryRequest | null;
|
|
196
198
|
export declare function decodeRecoveryContextQueryResponse(value: unknown): RecoveryContextQueryResponse | null;
|
|
199
|
+
export interface QueueReviseRequest {
|
|
200
|
+
v: typeof TURN_IPC_PROTOCOL_VERSION;
|
|
201
|
+
meshId: string;
|
|
202
|
+
taskId: string;
|
|
203
|
+
expectedRevision: number;
|
|
204
|
+
patch: {
|
|
205
|
+
message?: string;
|
|
206
|
+
ownedPaths?: string[];
|
|
207
|
+
phase?: 'source' | 'integration';
|
|
208
|
+
dependsOn?: string[];
|
|
209
|
+
routingPreferences?: import('./task-source-phase').MeshRoutingPreferences;
|
|
210
|
+
review?: import('./task-source-phase').MeshReviewConstraint;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
export type QueueReviseResponse = QueueRequeueResponse;
|
|
214
|
+
export declare function decodeQueueReviseRequest(value: unknown): QueueReviseRequest | null;
|
|
215
|
+
export declare const decodeQueueReviseResponse: typeof decodeQueueRequeueResponse;
|
package/dist/turn-ipc.d.ts
CHANGED
|
@@ -94,6 +94,6 @@ export interface TurnIpcError {
|
|
|
94
94
|
}
|
|
95
95
|
export declare function isTurnIpcError(value: unknown): value is TurnIpcError;
|
|
96
96
|
/** The complete, closed set of IPC command names this contract defines. */
|
|
97
|
-
export declare const TURN_IPC_COMMANDS: readonly ["turn_observe", "mesh_record", "turn_cancel", "operator_status", "turn_query", "mesh_index_query", "mission_upsert", "mission_query", "note_upsert", "note_forget", "tool_call_record", "ledger_query", "mission_list_query", "record_local", "queue_query", "queue_enqueue", "queue_enqueue_batch", "queue_cancel", "queue_requeue", "direct_dispatch_record", "active_work_query", "recovery_context_query", "task_stats_query", "prune_stale_direct", "orphaned_pin_notify"];
|
|
97
|
+
export declare const TURN_IPC_COMMANDS: readonly ["turn_observe", "mesh_record", "turn_cancel", "operator_status", "turn_query", "mesh_index_query", "mission_upsert", "mission_query", "note_upsert", "note_forget", "tool_call_record", "ledger_query", "mission_list_query", "record_local", "queue_query", "queue_enqueue", "queue_enqueue_batch", "queue_cancel", "queue_requeue", "queue_revise", "direct_dispatch_record", "active_work_query", "recovery_context_query", "task_stats_query", "prune_stale_direct", "orphaned_pin_notify"];
|
|
98
98
|
export type TurnIpcCommand = typeof TURN_IPC_COMMANDS[number];
|
|
99
|
-
export declare const isTurnIpcCommand: (value: unknown) => value is "prune_stale_direct" | "operator_status" | "turn_observe" | "mesh_record" | "turn_cancel" | "turn_query" | "mesh_index_query" | "mission_upsert" | "mission_query" | "note_upsert" | "note_forget" | "tool_call_record" | "ledger_query" | "mission_list_query" | "record_local" | "queue_query" | "queue_enqueue" | "queue_enqueue_batch" | "queue_cancel" | "queue_requeue" | "direct_dispatch_record" | "active_work_query" | "recovery_context_query" | "task_stats_query" | "orphaned_pin_notify";
|
|
99
|
+
export declare const isTurnIpcCommand: (value: unknown) => value is "prune_stale_direct" | "operator_status" | "turn_observe" | "mesh_record" | "turn_cancel" | "turn_query" | "mesh_index_query" | "mission_upsert" | "mission_query" | "note_upsert" | "note_forget" | "tool_call_record" | "ledger_query" | "mission_list_query" | "record_local" | "queue_query" | "queue_enqueue" | "queue_enqueue_batch" | "queue_cancel" | "queue_requeue" | "queue_revise" | "direct_dispatch_record" | "active_work_query" | "recovery_context_query" | "task_stats_query" | "orphaned_pin_notify";
|
package/dist/types.d.ts
CHANGED
|
@@ -120,6 +120,7 @@ export interface MeshNodeIdentified {
|
|
|
120
120
|
[key: string]: unknown;
|
|
121
121
|
}
|
|
122
122
|
export interface RepoMeshSessionStatus {
|
|
123
|
+
providerApproval?: import('./provider-approval').ProviderApproval | null;
|
|
123
124
|
sessionId: string;
|
|
124
125
|
providerType?: string;
|
|
125
126
|
state?: string;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the protocol contract the marker demarcates.
|
|
31
31
|
*/
|
|
32
32
|
import { type MissionBrief } from './mission-brief';
|
|
33
|
-
export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff"];
|
|
33
|
+
export declare const WORKER_TOOLS: readonly ["report_completion", "progress_update", "peer_context_pull", "git_status", "git_log", "git_diff", "driver_list", "driver_describe", "driver_invoke", "artifact_register"];
|
|
34
34
|
export type WorkerTool = typeof WORKER_TOOLS[number];
|
|
35
35
|
export declare function isWorkerTool(value: unknown): value is WorkerTool;
|
|
36
36
|
/** Stable marker line so a body is never footered twice and tests can find it. */
|
package/package.json
CHANGED
|
@@ -11,22 +11,29 @@
|
|
|
11
11
|
* - the daemon-only verbs (launch / MCP-only pull / owner actions) that have
|
|
12
12
|
* no tool.
|
|
13
13
|
*
|
|
14
|
-
* Assistant tools
|
|
15
|
-
*
|
|
16
|
-
* never publish them.
|
|
14
|
+
* Assistant-specific tools remain separate from mesh tools. Driver discovery
|
|
15
|
+
* (list/describe) is shared across all three modes; invoke remains Worker-only.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
export const ASSISTANT_TOOLS = [
|
|
20
19
|
'projects',
|
|
20
|
+
'environment',
|
|
21
|
+
'assistant_action',
|
|
22
|
+
'project_watch',
|
|
21
23
|
'project_status',
|
|
22
24
|
'project_send',
|
|
23
25
|
'project_read',
|
|
24
26
|
'project_add',
|
|
27
|
+
'project_attach_remote',
|
|
25
28
|
'discover_repos',
|
|
26
29
|
'memory',
|
|
27
30
|
'skill_view',
|
|
28
31
|
'skill_manage',
|
|
29
32
|
'project_note',
|
|
33
|
+
'launch_defaults',
|
|
34
|
+
'launch_defaults_set',
|
|
35
|
+
'driver_list',
|
|
36
|
+
'driver_describe',
|
|
30
37
|
] as const
|
|
31
38
|
|
|
32
39
|
export type AssistantTool = typeof ASSISTANT_TOOLS[number]
|
|
@@ -40,16 +47,26 @@ export function isAssistantTool(value: unknown): value is AssistantTool {
|
|
|
40
47
|
/** Daemon verb names (§4.4 table + the five verbs below it). */
|
|
41
48
|
export const ASSISTANT_VERB = {
|
|
42
49
|
// tool verbs
|
|
50
|
+
driverList: 'driver_list',
|
|
51
|
+
driverDescribe: 'driver_describe',
|
|
43
52
|
projects: 'assistant_projects',
|
|
53
|
+
environment: 'assistant_environment',
|
|
54
|
+
action: 'assistant_action',
|
|
55
|
+
watch: 'assistant_project_watch',
|
|
56
|
+
executionState: 'assistant_execution_state',
|
|
57
|
+
executionSettings: 'assistant_execution_settings',
|
|
44
58
|
projectStatus: 'assistant_project_status',
|
|
45
59
|
projectSend: 'assistant_project_send',
|
|
46
60
|
projectRead: 'assistant_project_read',
|
|
47
61
|
projectAdd: 'assistant_project_add',
|
|
62
|
+
projectAttachRemote: 'assistant_project_attach_remote',
|
|
48
63
|
discoverRepos: 'assistant_discover_repos',
|
|
49
64
|
memory: 'assistant_memory',
|
|
50
65
|
skillView: 'assistant_skill_view',
|
|
51
66
|
skillManage: 'assistant_skill_manage',
|
|
52
67
|
projectNote: 'assistant_project_note',
|
|
68
|
+
launchDefaults: 'assistant_launch_defaults',
|
|
69
|
+
launchDefaultsSet: 'assistant_launch_defaults_set',
|
|
53
70
|
// assistant-side daemon verbs without a tool of the same name
|
|
54
71
|
launch: 'launch_assistant',
|
|
55
72
|
pendingRelays: 'assistant_pending_relays',
|
|
@@ -57,22 +74,33 @@ export const ASSISTANT_VERB = {
|
|
|
57
74
|
stagedResolve: 'assistant_staged_resolve',
|
|
58
75
|
storeAdmin: 'assistant_store_admin',
|
|
59
76
|
importSkills: 'assistant_import_skills',
|
|
77
|
+
/** Projects the assistant is following (open relay threads) — list / stop following. */
|
|
78
|
+
threads: 'assistant_threads',
|
|
79
|
+
threadClose: 'assistant_thread_close',
|
|
60
80
|
} as const
|
|
61
81
|
|
|
62
82
|
export type AssistantVerb = typeof ASSISTANT_VERB[keyof typeof ASSISTANT_VERB]
|
|
63
83
|
|
|
64
84
|
/** The daemon verb each assistant tool calls. */
|
|
65
85
|
export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>> = {
|
|
86
|
+
driver_list: ASSISTANT_VERB.driverList,
|
|
87
|
+
driver_describe: ASSISTANT_VERB.driverDescribe,
|
|
66
88
|
projects: ASSISTANT_VERB.projects,
|
|
89
|
+
environment: ASSISTANT_VERB.environment,
|
|
90
|
+
assistant_action: ASSISTANT_VERB.action,
|
|
91
|
+
project_watch: ASSISTANT_VERB.watch,
|
|
67
92
|
project_status: ASSISTANT_VERB.projectStatus,
|
|
68
93
|
project_send: ASSISTANT_VERB.projectSend,
|
|
69
94
|
project_read: ASSISTANT_VERB.projectRead,
|
|
70
95
|
project_add: ASSISTANT_VERB.projectAdd,
|
|
96
|
+
project_attach_remote: ASSISTANT_VERB.projectAttachRemote,
|
|
71
97
|
discover_repos: ASSISTANT_VERB.discoverRepos,
|
|
72
98
|
memory: ASSISTANT_VERB.memory,
|
|
73
99
|
skill_view: ASSISTANT_VERB.skillView,
|
|
74
100
|
skill_manage: ASSISTANT_VERB.skillManage,
|
|
75
101
|
project_note: ASSISTANT_VERB.projectNote,
|
|
102
|
+
launch_defaults: ASSISTANT_VERB.launchDefaults,
|
|
103
|
+
launch_defaults_set: ASSISTANT_VERB.launchDefaultsSet,
|
|
76
104
|
}
|
|
77
105
|
|
|
78
106
|
/**
|
|
@@ -81,13 +109,15 @@ export const ASSISTANT_TOOL_VERBS: Readonly<Record<AssistantTool, AssistantVerb>
|
|
|
81
109
|
* because they cause effects (a coordinator message or launch, a new
|
|
82
110
|
* project), and MCP clients use readOnlyHint to skip confirmation.
|
|
83
111
|
*/
|
|
84
|
-
export const ASSISTANT_WRITE_TOOLS: readonly AssistantTool[] = ['memory', 'skill_manage', 'project_note', 'project_send', 'project_add']
|
|
112
|
+
export const ASSISTANT_WRITE_TOOLS: readonly AssistantTool[] = ['project_watch', 'assistant_action', 'memory', 'skill_manage', 'project_note', 'project_send', 'project_add', 'project_attach_remote', 'launch_defaults_set']
|
|
85
113
|
|
|
86
114
|
/** Owner-only verbs: never reachable from the assistant's MCP server (no `ipc`). */
|
|
87
115
|
export const ASSISTANT_OWNER_VERBS: readonly AssistantVerb[] = [
|
|
116
|
+
ASSISTANT_VERB.executionSettings,
|
|
88
117
|
ASSISTANT_VERB.stagedResolve,
|
|
89
118
|
ASSISTANT_VERB.storeAdmin,
|
|
90
119
|
ASSISTANT_VERB.importSkills,
|
|
120
|
+
ASSISTANT_VERB.threadClose,
|
|
91
121
|
]
|
|
92
122
|
|
|
93
123
|
/** The only assistant verbs accepted while an idle review turn is open (§4.10.7). */
|
|
@@ -98,8 +128,9 @@ export const ASSISTANT_REVIEW_TURN_VERBS: readonly AssistantVerb[] = [
|
|
|
98
128
|
ASSISTANT_VERB.projectNote,
|
|
99
129
|
]
|
|
100
130
|
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
import { canonicalDaemonId } from './daemon-normalize'
|
|
132
|
+
|
|
133
|
+
/** Env var the daemon sets on the assistant CLI's process (§4.5), read by the
|
|
103
134
|
* assistant MCP server and forwarded on every verb as
|
|
104
135
|
* `ASSISTANT_SESSION_ID_ARG`. Routing / ownership only — never an auth gate.
|
|
105
136
|
*/
|
|
@@ -107,3 +138,213 @@ export const ASSISTANT_SESSION_ID_ENV = 'ADHDEV_ASSISTANT_SESSION_ID'
|
|
|
107
138
|
|
|
108
139
|
/** Request arg carrying the calling assistant session id. */
|
|
109
140
|
export const ASSISTANT_SESSION_ID_ARG = 'assistantSessionId'
|
|
141
|
+
|
|
142
|
+
// ─── Remote Assistant Project Follow & Host-Authoritative Grant (rc24) ──────
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Conceptual single-assistant slot in the verified account namespace.
|
|
146
|
+
* Fixed to `'personal'` — one logical assistant per account with one
|
|
147
|
+
* owner-selected active execution daemon binding per project ACL.
|
|
148
|
+
*/
|
|
149
|
+
export const ASSISTANT_ACCOUNT_SLOT = 'personal' as const
|
|
150
|
+
export type AssistantAccountSlot = typeof ASSISTANT_ACCOUNT_SLOT
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Daemon commands for remote project discovery, host ACL grant/revoke, and
|
|
154
|
+
* assistant-side follow/unfollow. Registered outside `ASSISTANT_VERB` so the
|
|
155
|
+
* assistant MCP server tool/verb parity table remains untouched.
|
|
156
|
+
*/
|
|
157
|
+
export const ASSISTANT_REMOTE_PROJECT_COMMAND = 'assistant_remote_project' as const
|
|
158
|
+
export const ASSISTANT_REMOTE_PROJECT_CATALOG_COMMAND = 'assistant_remote_project_catalog' as const
|
|
159
|
+
export const ASSISTANT_REMOTE_PROJECT_ACCESS_COMMAND = 'assistant_remote_project_access' as const
|
|
160
|
+
export const ASSISTANT_REMOTE_FOLLOW_COMMAND = 'assistant_remote_follow' as const
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Narrow operations a non-roster follower deputy is permitted to invoke on
|
|
164
|
+
* `assistant_remote_project` when the host's `assistantRemoteAccess` ACL is active.
|
|
165
|
+
* `note`, `terminal`, and `inventory` remain strictly roster-member-only.
|
|
166
|
+
*/
|
|
167
|
+
export const ASSISTANT_FOLLOWER_ALLOWED_OPS = ['status', 'read', 'poll', 'report', 'send'] as const
|
|
168
|
+
export type AssistantFollowerAllowedOp = typeof ASSISTANT_FOLLOWER_ALLOWED_OPS[number]
|
|
169
|
+
|
|
170
|
+
const ASSISTANT_FOLLOWER_ALLOWED_OP_SET: ReadonlySet<string> = new Set(ASSISTANT_FOLLOWER_ALLOWED_OPS)
|
|
171
|
+
|
|
172
|
+
export function isAssistantFollowerAllowedOp(value: unknown): value is AssistantFollowerAllowedOp {
|
|
173
|
+
return typeof value === 'string' && ASSISTANT_FOLLOWER_ALLOWED_OP_SET.has(value)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Host-authoritative narrow project ACL persisted on the hosting daemon's
|
|
178
|
+
* `LocalMeshEntry` in `meshes.json`. Revocation increments `grantRevision`
|
|
179
|
+
* and retains a `'revoked'` tombstone so stale follower caches cannot replay.
|
|
180
|
+
*/
|
|
181
|
+
export interface AssistantRemoteAccessBinding {
|
|
182
|
+
hostDaemonId: string
|
|
183
|
+
meshId: string
|
|
184
|
+
accountSlot: AssistantAccountSlot
|
|
185
|
+
activeDaemonId: string
|
|
186
|
+
bindingRevision: number
|
|
187
|
+
grantRevision: number
|
|
188
|
+
status: 'active' | 'revoked'
|
|
189
|
+
allowedOps: readonly AssistantFollowerAllowedOp[]
|
|
190
|
+
allowCoordinatorLaunch?: boolean
|
|
191
|
+
grantedAt: number
|
|
192
|
+
revokedAt?: number
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Durable owner-binding receipt persisted in the assistant daemon's
|
|
197
|
+
* `assistant.json`. Survives ordinary daemon restarts and session/provider
|
|
198
|
+
* replacements (`bindSession`), while missing/corrupt/mismatched receipts
|
|
199
|
+
* fail closed until owner reconciliation.
|
|
200
|
+
*/
|
|
201
|
+
export interface AssistantOwnerBindingReceipt {
|
|
202
|
+
accountSlot: AssistantAccountSlot
|
|
203
|
+
daemonId: string
|
|
204
|
+
bindingRevision: number
|
|
205
|
+
boundAt: number | string
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type FollowedRemoteProjectState = 'followed' | 'unfollowed' | 'revoked'
|
|
209
|
+
|
|
210
|
+
export type RemoteAccessState =
|
|
211
|
+
| 'not_granted'
|
|
212
|
+
| 'granted_unfollowed'
|
|
213
|
+
| 'followed'
|
|
214
|
+
| 'revoked'
|
|
215
|
+
| 'host_unreachable'
|
|
216
|
+
| 'binding_mismatch'
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Assistant-local followed remote project reference persisted in `assistant.json`.
|
|
220
|
+
* Never injected into `meshes.json`, `mesh-host-records.json`, `mesh-node-state-push.json`,
|
|
221
|
+
* or `inlineMeshCache`.
|
|
222
|
+
*/
|
|
223
|
+
export interface FollowedRemoteProjectEntry {
|
|
224
|
+
/** Canonical key: `${canonicalDaemonId(hostDaemonId)}::${meshId}` */
|
|
225
|
+
key: string
|
|
226
|
+
hostDaemonId: string
|
|
227
|
+
hostLabel?: string
|
|
228
|
+
meshId: string
|
|
229
|
+
name: string
|
|
230
|
+
slug: string
|
|
231
|
+
sanitizedRepoIdentity: string | null
|
|
232
|
+
repoIdentity?: string
|
|
233
|
+
accountSlot: AssistantAccountSlot
|
|
234
|
+
bindingRevision: number
|
|
235
|
+
grantRevision: number
|
|
236
|
+
allowCoordinatorLaunch?: boolean
|
|
237
|
+
state: FollowedRemoteProjectState
|
|
238
|
+
followedAt: number | string
|
|
239
|
+
updatedAt: number | string
|
|
240
|
+
unfollowedAt?: number | string
|
|
241
|
+
revokedAt?: number | string
|
|
242
|
+
revokedReason?: string
|
|
243
|
+
lastCursor?: string | null
|
|
244
|
+
coordinatorSessionId?: string | null
|
|
245
|
+
lastErrorCode?: string
|
|
246
|
+
lastObserved?: {
|
|
247
|
+
observedAt: number | string
|
|
248
|
+
coordinatorState?: string
|
|
249
|
+
coordinatorMissing?: boolean
|
|
250
|
+
coordinatorPresent?: boolean
|
|
251
|
+
lastHeadline?: string
|
|
252
|
+
summary?: string
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Content-free canonical catalog metadata returned by `assistant_remote_project_catalog`
|
|
258
|
+
* for meshes actually hosted on the queried remote daemon.
|
|
259
|
+
*/
|
|
260
|
+
export interface RemoteProjectCatalogItem {
|
|
261
|
+
hostDaemonId: string
|
|
262
|
+
meshId: string
|
|
263
|
+
name: string
|
|
264
|
+
sanitizedRepoIdentity: string | null
|
|
265
|
+
coordinatorState?: 'none' | 'idle' | 'working' | 'waiting' | string
|
|
266
|
+
allowCoordinatorLaunch?: boolean
|
|
267
|
+
capabilities: {
|
|
268
|
+
status: true
|
|
269
|
+
read: true
|
|
270
|
+
poll: true
|
|
271
|
+
report: true
|
|
272
|
+
send: true
|
|
273
|
+
coordinatorPresent?: boolean
|
|
274
|
+
allowCoordinatorLaunch?: boolean
|
|
275
|
+
}
|
|
276
|
+
observedAt: number | string
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Build the canonical `(hostDaemonId, meshId)` key used across assistant follow state. */
|
|
280
|
+
export function followedRemoteProjectKey(hostDaemonId: string, meshId: string): string {
|
|
281
|
+
const canonHost = canonicalDaemonId(hostDaemonId) ?? hostDaemonId.trim()
|
|
282
|
+
return `${canonHost}::${meshId.trim()}`
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Sanitize a repository identity or remote URL into a content-free `host/path`
|
|
287
|
+
* identifier suitable for remote discovery catalogs. Strips credentials, query
|
|
288
|
+
* strings, fragments, and `.git` suffixes, and rejects local filesystem paths.
|
|
289
|
+
*/
|
|
290
|
+
export function sanitizeRemoteRepoIdentity(raw: string | null | undefined): string | null {
|
|
291
|
+
if (typeof raw !== 'string') return null
|
|
292
|
+
const trimmed = raw.trim()
|
|
293
|
+
if (!trimmed) return null
|
|
294
|
+
|
|
295
|
+
// Reject local filesystem paths (Unix absolute/relative/home, Windows drive/UNC, file:// URLs).
|
|
296
|
+
if (
|
|
297
|
+
trimmed.startsWith('/')
|
|
298
|
+
|| trimmed.startsWith('./')
|
|
299
|
+
|| trimmed.startsWith('../')
|
|
300
|
+
|| trimmed.startsWith('~/')
|
|
301
|
+
|| trimmed.startsWith('\\')
|
|
302
|
+
|| /^[a-zA-Z]:[\\/]/.test(trimmed)
|
|
303
|
+
|| /^file:/i.test(trimmed)
|
|
304
|
+
) {
|
|
305
|
+
return null
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const withoutQuery = trimmed.replace(/[?#].*$/, '').replace(/\/+$/, '')
|
|
309
|
+
if (!withoutQuery) return null
|
|
310
|
+
|
|
311
|
+
let host = ''
|
|
312
|
+
let repoPath = ''
|
|
313
|
+
|
|
314
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(withoutQuery)) {
|
|
315
|
+
try {
|
|
316
|
+
const url = new URL(withoutQuery)
|
|
317
|
+
if (url.protocol === 'file:') return null
|
|
318
|
+
host = url.hostname.toLowerCase()
|
|
319
|
+
repoPath = decodeURIComponent(url.pathname)
|
|
320
|
+
.replace(/^\/+|\/+$/g, '')
|
|
321
|
+
.replace(/\.git$/i, '')
|
|
322
|
+
} catch {
|
|
323
|
+
return null
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
const scpMatch = withoutQuery.match(/^(?:[^@/:]+@)?(\[[^\]]+\]|[^/:]+):(.+)$/)
|
|
327
|
+
if (scpMatch) {
|
|
328
|
+
host = scpMatch[1].replace(/^\[|\]$/g, '').toLowerCase()
|
|
329
|
+
repoPath = scpMatch[2].replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
|
|
330
|
+
} else {
|
|
331
|
+
const slash = withoutQuery.indexOf('/')
|
|
332
|
+
if (slash > 0) {
|
|
333
|
+
const rawHost = withoutQuery.slice(0, slash)
|
|
334
|
+
const atIdx = rawHost.lastIndexOf('@')
|
|
335
|
+
host = (atIdx >= 0 ? rawHost.slice(atIdx + 1) : rawHost).toLowerCase()
|
|
336
|
+
repoPath = withoutQuery.slice(slash + 1).replace(/^\/+|\/+$/g, '').replace(/\.git$/i, '')
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!host || !repoPath) return null
|
|
342
|
+
if (!host.includes('.') && host !== 'localhost') return null
|
|
343
|
+
if (/[\s@:\\]/.test(host)) return null
|
|
344
|
+
const segments = repoPath.split('/').filter(Boolean)
|
|
345
|
+
if (segments.length === 0) return null
|
|
346
|
+
if (segments.some(seg => seg === '.' || seg === '..' || /[\s@:\\]/.test(seg))) return null
|
|
347
|
+
|
|
348
|
+
return `${host}/${segments.join('/')}`.slice(0, 200)
|
|
349
|
+
}
|
|
350
|
+
|
package/src/daemon-normalize.ts
CHANGED
|
@@ -108,13 +108,15 @@ export function isPhantomDaemonEntry(
|
|
|
108
108
|
export function machineCoreFromDaemonId(id: string | null | undefined): string | undefined {
|
|
109
109
|
const trimmed = readString(id)
|
|
110
110
|
if (!trimmed) return undefined
|
|
111
|
+
const lower = trimmed.toLowerCase()
|
|
111
112
|
for (const prefix of DAEMON_ID_PREFIXES) {
|
|
112
|
-
if (
|
|
113
|
+
if (lower.startsWith(prefix)) {
|
|
113
114
|
const core = trimmed.slice(prefix.length).trim()
|
|
114
|
-
|
|
115
|
+
if (!core) return undefined
|
|
116
|
+
return /^mach_/i.test(core) ? `mach_${core.slice(5)}` : core
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
|
-
return trimmed
|
|
119
|
+
return /^mach_/i.test(trimmed) ? `mach_${trimmed.slice(5)}` : trimmed
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
/**
|
|
@@ -144,8 +146,8 @@ export function machineCoreFromDaemonId(id: string | null | undefined): string |
|
|
|
144
146
|
export function canonicalDaemonId(id: string | null | undefined): string | undefined {
|
|
145
147
|
const core = machineCoreFromDaemonId(id)
|
|
146
148
|
if (!core) return undefined
|
|
147
|
-
if (
|
|
148
|
-
return `
|
|
149
|
+
if (!/^mach_/i.test(core)) return core
|
|
150
|
+
return `daemon_mach_${core.slice(5)}`
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
/** True when both ids resolve to the same machine core — i.e. they are the same
|