@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
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from './git-normalize'
|
|
|
16
16
|
export * from './session-normalize'
|
|
17
17
|
export * from './node-normalize'
|
|
18
18
|
export * from './node-facts'
|
|
19
|
+
export * from './quota-freshness'
|
|
19
20
|
export * from './workspace-normalize'
|
|
20
21
|
export * from './daemon-normalize'
|
|
21
22
|
export * from './git-summarize'
|
|
@@ -41,3 +42,9 @@ export * from './turn-ipc'
|
|
|
41
42
|
export * from './path-ownership'
|
|
42
43
|
export * from './mission-brief'
|
|
43
44
|
export * from './keyed-doc-delta'
|
|
45
|
+
export * from './provider-approval'
|
|
46
|
+
export * from './provider-question'
|
|
47
|
+
export * from './launch-defaults'
|
|
48
|
+
|
|
49
|
+
export * from './role-launch';
|
|
50
|
+
export * from './task-source-phase';
|
package/src/keyed-doc-delta.ts
CHANGED
|
@@ -326,7 +326,15 @@ export function foldKeyedDoc<T extends Record<string, unknown>>(held: T, delta:
|
|
|
326
326
|
const heldList = heldCollections.get(path)
|
|
327
327
|
if (change?.absent) {
|
|
328
328
|
const [head, tail] = splitPath(path)
|
|
329
|
-
if (!tail)
|
|
329
|
+
if (!tail) {
|
|
330
|
+
delete next[head]
|
|
331
|
+
continue
|
|
332
|
+
}
|
|
333
|
+
const parent = next[head]
|
|
334
|
+
if (!isRecord(parent) || !(tail in parent)) continue
|
|
335
|
+
const copy = { ...parent }
|
|
336
|
+
delete copy[tail]
|
|
337
|
+
next[head] = copy
|
|
330
338
|
continue
|
|
331
339
|
}
|
|
332
340
|
if (!change && heldList === undefined) continue
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Daemon-local launch intent and command wire contract. P2P/local only;
|
|
2
|
+
* never include this document or its history in cloud status_report.
|
|
3
|
+
* Publication through mesh-shared's barrel is a later integration step. */
|
|
4
|
+
export const LAUNCH_DEFAULT_ROLES = ['assistant', 'coordinator'] as const;
|
|
5
|
+
export type LaunchDefaultRole = typeof LAUNCH_DEFAULT_ROLES[number];
|
|
6
|
+
export const LAUNCH_DEFAULT_SOURCES = ['user', 'stored_default', 'project_default', 'provider_default'] as const;
|
|
7
|
+
export type LaunchDefaultSource = typeof LAUNCH_DEFAULT_SOURCES[number];
|
|
8
|
+
export const LAUNCH_DEFAULTS_NEXT_LAUNCH_NOTE = 'Applies to the next launch; running sessions are unchanged.';
|
|
9
|
+
export function isLaunchDefaultRole(value: unknown): value is LaunchDefaultRole {
|
|
10
|
+
return typeof value === 'string' && (LAUNCH_DEFAULT_ROLES as readonly string[]).includes(value);
|
|
11
|
+
}
|
|
12
|
+
export function isLaunchDefaultSource(value: unknown): value is LaunchDefaultSource {
|
|
13
|
+
return typeof value === 'string' && (LAUNCH_DEFAULT_SOURCES as readonly string[]).includes(value);
|
|
14
|
+
}
|
|
15
|
+
export interface LaunchDefaultSelection {
|
|
16
|
+
provider: string;
|
|
17
|
+
/** Omission means pass no model/thinking argument, not inherit another provider. */
|
|
18
|
+
model?: string;
|
|
19
|
+
thinkingLevel?: string;
|
|
20
|
+
}
|
|
21
|
+
export type LaunchDefaultToolRestriction = 'enforced' | 'prompt_only';
|
|
22
|
+
export type LaunchDefaultOrigin = 'owner' | 'human' | 'relay' | 'review' | 'review_tainted';
|
|
23
|
+
export interface LaunchDefaultEntry extends LaunchDefaultSelection {
|
|
24
|
+
revision: number;
|
|
25
|
+
setBy: 'owner' | 'assistant';
|
|
26
|
+
setAt: number;
|
|
27
|
+
}
|
|
28
|
+
export interface LaunchDefaultChange {
|
|
29
|
+
at: number;
|
|
30
|
+
role: LaunchDefaultRole;
|
|
31
|
+
by: 'owner' | 'assistant';
|
|
32
|
+
origin: LaunchDefaultOrigin;
|
|
33
|
+
from: LaunchDefaultEntry | null;
|
|
34
|
+
to: LaunchDefaultEntry | null;
|
|
35
|
+
}
|
|
36
|
+
export interface LaunchDefaultsDocument {
|
|
37
|
+
version: 1;
|
|
38
|
+
/** Global document token, including clears; distinct from entry revisions. */
|
|
39
|
+
revision: number;
|
|
40
|
+
assistant?: LaunchDefaultEntry;
|
|
41
|
+
coordinator?: LaunchDefaultEntry;
|
|
42
|
+
history: LaunchDefaultChange[];
|
|
43
|
+
}
|
|
44
|
+
export interface LaunchDefaultRef {
|
|
45
|
+
role: LaunchDefaultRole;
|
|
46
|
+
scope: 'global' | 'project';
|
|
47
|
+
/** Entry revision for global defaults; host project token for project defaults. */
|
|
48
|
+
revision: number;
|
|
49
|
+
projectId?: string;
|
|
50
|
+
}
|
|
51
|
+
export type LaunchDefaultField = keyof LaunchDefaultSelection;
|
|
52
|
+
export interface LaunchDefaultAxis {
|
|
53
|
+
value: string | null;
|
|
54
|
+
source: LaunchDefaultSource;
|
|
55
|
+
defaultRef?: LaunchDefaultRef;
|
|
56
|
+
}
|
|
57
|
+
export type LaunchDefaultWarning = 'catalog_unverified' | 'manual_mcp_setup_required';
|
|
58
|
+
export type LaunchDefaultValidationReason =
|
|
59
|
+
| 'invalid_schema' | 'provider_unknown' | 'provider_not_cli' | 'provider_disabled'
|
|
60
|
+
| 'provider_not_detected' | 'provider_not_eligible' | 'provider_quota_gated' | 'model_unsupported'
|
|
61
|
+
| 'model_provider_mismatch' | 'model_not_offered' | 'thinking_unsupported'
|
|
62
|
+
| 'thinking_not_offered' | 'catalog_unavailable';
|
|
63
|
+
export type LaunchDefaultResolution =
|
|
64
|
+
| { ok: true; selection: LaunchDefaultSelection; applied: Record<LaunchDefaultField, LaunchDefaultAxis>;
|
|
65
|
+
defaultRef?: LaunchDefaultRef; documentRevision: number; warnings: LaunchDefaultWarning[];
|
|
66
|
+
toolRestriction: LaunchDefaultToolRestriction }
|
|
67
|
+
| { ok: false; code: 'launch_default_required' | 'launch_default_unavailable' | 'launch_selection_invalid';
|
|
68
|
+
role: LaunchDefaultRole; field: LaunchDefaultField; reason: string; source?: LaunchDefaultSource;
|
|
69
|
+
value?: string | null; defaultRef?: LaunchDefaultRef; candidates?: readonly string[]; hint: string };
|
|
70
|
+
export interface LaunchDefaultsRoleView {
|
|
71
|
+
stored: LaunchDefaultEntry | null;
|
|
72
|
+
/** The existing daemon resolver's result, including loud failures. */
|
|
73
|
+
effective: LaunchDefaultResolution;
|
|
74
|
+
}
|
|
75
|
+
type Target = { role: 'assistant'; project?: never } | { role: 'coordinator'; project?: string };
|
|
76
|
+
export type LaunchDefaultsReadRequest =
|
|
77
|
+
| ({ action?: 'get' } & (Target | { role?: never; project?: never }))
|
|
78
|
+
| ({ action: 'check' } & Target & LaunchDefaultSelection);
|
|
79
|
+
/** Mandatory CAS: global document token, or host project token for project writes.
|
|
80
|
+
* 0 is explicit first setup; clears advance the token and never restore fallback. */
|
|
81
|
+
export type LaunchDefaultsSetRequest = Target & { expectedRevision: number } & (
|
|
82
|
+
| ({ action: 'set' } & LaunchDefaultSelection)
|
|
83
|
+
| { action: 'clear'; provider?: never; model?: never; thinkingLevel?: never }
|
|
84
|
+
);
|
|
85
|
+
/** Existing assistant routing envelope; session identity never supplies origin. */
|
|
86
|
+
export type AssistantLaunchDefaultsReadRequest = LaunchDefaultsReadRequest & { assistantSessionId?: string };
|
|
87
|
+
export type AssistantLaunchDefaultsSetRequest = LaunchDefaultsSetRequest & { assistantSessionId?: string };
|
|
88
|
+
export const LAUNCH_DEFAULT_ERROR_CODES = [
|
|
89
|
+
'invalid_args', 'launch_default_required', 'launch_default_unavailable', 'launch_selection_invalid',
|
|
90
|
+
'launch_default_invalid', 'revision_conflict', 'store_unavailable', 'store_busy', 'persistence_failed',
|
|
91
|
+
'project_not_found', 'project_ambiguous', 'project_not_hosted', 'project_storage_unavailable',
|
|
92
|
+
'staging_unavailable', 'staging_failed', 'review_turn_tool_denied',
|
|
93
|
+
] as const;
|
|
94
|
+
export type LaunchDefaultsErrorCode = typeof LAUNCH_DEFAULT_ERROR_CODES[number];
|
|
95
|
+
export interface LaunchDefaultsError {
|
|
96
|
+
success: false;
|
|
97
|
+
code: LaunchDefaultsErrorCode;
|
|
98
|
+
error: string;
|
|
99
|
+
reason?: string;
|
|
100
|
+
role?: LaunchDefaultRole;
|
|
101
|
+
field?: LaunchDefaultField;
|
|
102
|
+
source?: LaunchDefaultSource;
|
|
103
|
+
value?: string | null;
|
|
104
|
+
defaultRef?: LaunchDefaultRef;
|
|
105
|
+
candidates?: readonly string[];
|
|
106
|
+
actualRevision?: number;
|
|
107
|
+
/** 0 for an absent global document; absent when it cannot be read. */
|
|
108
|
+
documentRevision?: number;
|
|
109
|
+
/** A persistence failure can replace intent without confirming durability.
|
|
110
|
+
* Re-read before retrying; never report applied on committed:true alone. */
|
|
111
|
+
committed?: boolean;
|
|
112
|
+
}
|
|
113
|
+
export interface LaunchDefaultsReadSuccess {
|
|
114
|
+
success: true;
|
|
115
|
+
result: 'get';
|
|
116
|
+
documentRevision: number;
|
|
117
|
+
roles: Partial<Record<LaunchDefaultRole, LaunchDefaultsRoleView>>;
|
|
118
|
+
project?: { projectId: string; revision: number; entry: LaunchDefaultEntry | null };
|
|
119
|
+
}
|
|
120
|
+
export interface LaunchDefaultsCheckSuccess {
|
|
121
|
+
success: true;
|
|
122
|
+
result: 'check';
|
|
123
|
+
role: LaunchDefaultRole;
|
|
124
|
+
selection: LaunchDefaultSelection;
|
|
125
|
+
warnings: LaunchDefaultWarning[];
|
|
126
|
+
toolRestriction: LaunchDefaultToolRestriction;
|
|
127
|
+
lowersToolLock: boolean;
|
|
128
|
+
}
|
|
129
|
+
export interface LaunchDefaultsApplied {
|
|
130
|
+
success: true;
|
|
131
|
+
result: 'applied';
|
|
132
|
+
role: LaunchDefaultRole;
|
|
133
|
+
scope: 'global' | 'project';
|
|
134
|
+
projectId?: string;
|
|
135
|
+
entry: LaunchDefaultEntry | null;
|
|
136
|
+
previous: LaunchDefaultEntry | null;
|
|
137
|
+
/** CAS token returned by the authority that performed the write. */
|
|
138
|
+
revision: number;
|
|
139
|
+
warnings: LaunchDefaultWarning[];
|
|
140
|
+
durable: true;
|
|
141
|
+
note: string;
|
|
142
|
+
}
|
|
143
|
+
export interface LaunchDefaultsStaged {
|
|
144
|
+
success: true;
|
|
145
|
+
result: 'staged';
|
|
146
|
+
role: LaunchDefaultRole;
|
|
147
|
+
stagedId: string;
|
|
148
|
+
previous: LaunchDefaultEntry | null;
|
|
149
|
+
selection: LaunchDefaultSelection | null;
|
|
150
|
+
expectedRevision: number;
|
|
151
|
+
warnings: LaunchDefaultWarning[];
|
|
152
|
+
note: string;
|
|
153
|
+
}
|
|
154
|
+
/** Existing owner Inbox adapter stores this proposal; it is not another default.
|
|
155
|
+
* Applying it must revalidate on the host and CAS via the same set handler. */
|
|
156
|
+
export interface LaunchDefaultsProposal {
|
|
157
|
+
kind: 'launch_defaults';
|
|
158
|
+
role: LaunchDefaultRole;
|
|
159
|
+
project?: string;
|
|
160
|
+
selection: LaunchDefaultSelection | null;
|
|
161
|
+
previous: LaunchDefaultEntry | null;
|
|
162
|
+
expectedRevision: number;
|
|
163
|
+
origin: LaunchDefaultOrigin;
|
|
164
|
+
callerSessionId?: string;
|
|
165
|
+
}
|
|
166
|
+
export type LaunchDefaultsReadResponse = LaunchDefaultsReadSuccess | LaunchDefaultsCheckSuccess | LaunchDefaultsError;
|
|
167
|
+
export type LaunchDefaultsSetResponse = LaunchDefaultsApplied | LaunchDefaultsStaged | LaunchDefaultsError;
|
|
168
|
+
export type LaunchDefaultsResponse = LaunchDefaultsReadResponse | LaunchDefaultsSetResponse;
|
package/src/mesh-tool-names.ts
CHANGED
|
@@ -28,6 +28,7 @@ export const CANONICAL_MESH_TOOL_NAMES = [
|
|
|
28
28
|
'mesh_view_queue',
|
|
29
29
|
'mesh_queue_cancel',
|
|
30
30
|
'mesh_queue_requeue',
|
|
31
|
+
'mesh_queue_revise',
|
|
31
32
|
'mesh_send_task',
|
|
32
33
|
'mesh_read_chat',
|
|
33
34
|
'mesh_read_debug',
|
|
@@ -44,6 +45,7 @@ export const CANONICAL_MESH_TOOL_NAMES = [
|
|
|
44
45
|
'mesh_list_pending_approvals',
|
|
45
46
|
'mesh_create',
|
|
46
47
|
'mesh_add_node',
|
|
48
|
+
'mesh_attach_remote',
|
|
47
49
|
'mesh_clone_node',
|
|
48
50
|
'mesh_remove_node',
|
|
49
51
|
'mesh_cleanup_worktree_nodes',
|
|
@@ -64,6 +66,9 @@ export const CANONICAL_MESH_TOOL_NAMES = [
|
|
|
64
66
|
'mesh_mission_list',
|
|
65
67
|
'mesh_review_inbox',
|
|
66
68
|
'mesh_node_slots',
|
|
69
|
+
'driver_list',
|
|
70
|
+
'driver_describe',
|
|
71
|
+
'artifact_list',
|
|
67
72
|
] as const;
|
|
68
73
|
|
|
69
74
|
export type CanonicalMeshToolName = typeof CANONICAL_MESH_TOOL_NAMES[number];
|
package/src/mesh-vocabulary.ts
CHANGED
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
export { MESH_TASK_DIFFICULTIES, type MeshTaskDifficulty } from './brain-routing'
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/** `blocked` ends a worker attempt pending external follow-up; only completed satisfies dependencies.
|
|
21
|
+
* Branch convergence (including blocked_review) remains an independent axis. */
|
|
22
|
+
export const MESH_TASK_STATUSES = ['pending', 'assigned', 'completed', 'blocked', 'failed', 'cancelled'] as const
|
|
21
23
|
export type MeshTaskStatus = typeof MESH_TASK_STATUSES[number]
|
|
22
24
|
|
|
23
|
-
export const MESH_TERMINAL_TASK_STATUSES = ['completed', 'failed', 'cancelled'] as const
|
|
25
|
+
export const MESH_TERMINAL_TASK_STATUSES = ['completed', 'blocked', 'failed', 'cancelled'] as const
|
|
24
26
|
export type MeshTerminalTaskStatus = typeof MESH_TERMINAL_TASK_STATUSES[number]
|
|
25
27
|
|
|
26
28
|
export const MESH_TASK_MODES = ['code_change', 'validation', 'live_debug_readonly', 'launch_app', 'convergence'] as const
|
|
@@ -56,6 +58,14 @@ export type MeshSessionCleanupMode = typeof MESH_SESSION_CLEANUP_MODES[number]
|
|
|
56
58
|
export const WORKER_REPORT_OUTCOMES = ['completed', 'blocked', 'failed'] as const
|
|
57
59
|
export type WorkerReportOutcome = typeof WORKER_REPORT_OUTCOMES[number]
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Hard cap on a `report_completion` summary (characters, after trim). Over-limit
|
|
63
|
+
* summaries are REJECTED, never truncated. Declared once so the daemon validator
|
|
64
|
+
* and the mcp-server's pre-send check (which keeps an over-limit report out of the
|
|
65
|
+
* durable outbox) cannot drift apart.
|
|
66
|
+
*/
|
|
67
|
+
export const WORKER_REPORT_SUMMARY_MAX_CHARS = 8_000
|
|
68
|
+
|
|
59
69
|
/** Final branch state a worker reports (see coordinator convergence rules). */
|
|
60
70
|
export const WORKER_BRANCH_STATES = [
|
|
61
71
|
'merged_to_main',
|
package/src/node-facts.ts
CHANGED
|
@@ -97,6 +97,20 @@ export interface MeshNodeFactsProviderQuota {
|
|
|
97
97
|
* rather than presenting them as a freshly measured value.
|
|
98
98
|
*/
|
|
99
99
|
lastGoodWindows?: boolean
|
|
100
|
+
/**
|
|
101
|
+
* Unix ms the reporting node's NEXT scheduled retry of this transient
|
|
102
|
+
* failure is due (HTTP Retry-After wins when later). Re-stamped to the
|
|
103
|
+
* real backoff time on every failure — see daemon-core
|
|
104
|
+
* `updateFailureRetry`. Absent on persistent failures and on daemons
|
|
105
|
+
* predating the stamp.
|
|
106
|
+
*/
|
|
107
|
+
retryAtMs?: number
|
|
108
|
+
/**
|
|
109
|
+
* True once the node has spent its bounded transient-retry budget and
|
|
110
|
+
* stopped retrying; the next look is the hourly backfill. Readers must
|
|
111
|
+
* stop calling retained numbers "refreshing" (`assessQuotaFreshness`).
|
|
112
|
+
*/
|
|
113
|
+
retryExhausted?: boolean
|
|
100
114
|
/**
|
|
101
115
|
* Unix ms when the reporting node last ATTEMPTED a refresh of this
|
|
102
116
|
* provider — deliberately distinct from `updatedAt`, which dates the
|
|
@@ -112,6 +126,25 @@ export interface MeshNodeFactsProviderQuota {
|
|
|
112
126
|
* predating the field.
|
|
113
127
|
*/
|
|
114
128
|
fetchedAt?: number
|
|
129
|
+
/**
|
|
130
|
+
* Provenance of the RETAINED reading when `lastGoodWindows` is true —
|
|
131
|
+
* the source/plan/measurement time of the snapshot that produced the
|
|
132
|
+
* carried numbers, as opposed to the top-level `source`/`planType`,
|
|
133
|
+
* which describe the fresh failed attempt. See daemon-core
|
|
134
|
+
* `QuotaMetadata.lastGood`.
|
|
135
|
+
*/
|
|
136
|
+
lastGood?: {
|
|
137
|
+
measuredAt: number
|
|
138
|
+
source?: string
|
|
139
|
+
planType?: string | null
|
|
140
|
+
collectorScope?: string
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* `'background-collector'` when the snapshot came from a daemon-spawned
|
|
144
|
+
* probe rather than an executing CLI session; a failure with this scope
|
|
145
|
+
* is not evidence about a running session's sign-in.
|
|
146
|
+
*/
|
|
147
|
+
collectorScope?: string
|
|
115
148
|
[extra: string]: unknown
|
|
116
149
|
}
|
|
117
150
|
[extra: string]: unknown
|
|
@@ -137,6 +170,8 @@ export interface MeshNodeFactsProviderEnablement {
|
|
|
137
170
|
|
|
138
171
|
export interface MeshNodeFacts {
|
|
139
172
|
schemaVersion: number
|
|
173
|
+
/** Cached CLI detection only; no paths or account data. */
|
|
174
|
+
providerInventory?: { observedAt: number; providers: Record<string, { installed: boolean }> }
|
|
140
175
|
reportedAt: number
|
|
141
176
|
daemonBuild?: MeshNodeFactsDaemonBuild
|
|
142
177
|
providerVersions?: Record<string, string>
|
|
@@ -253,7 +288,7 @@ export function normalizeMeshNodeFacts(raw: unknown): MeshNodeFacts | undefined
|
|
|
253
288
|
* the host `agy` itself uses; the unprefixed `cloudcode-pa` host 429s Google
|
|
254
289
|
* AI Pro Antigravity accounts), and its credential lives in the OS keyring,
|
|
255
290
|
* not the stale on-disk token file — see the provenance note in daemon-core
|
|
256
|
-
* `quota/fetchers/antigravity.ts`. Supported on macOS and
|
|
291
|
+
* `quota/fetchers/antigravity.ts`. Supported on macOS, Windows, and Linux; other
|
|
257
292
|
* platforms report `unsupported` rather than guess at a keyring backend
|
|
258
293
|
* nobody has verified.
|
|
259
294
|
*
|
package/src/outbound-message.ts
CHANGED
|
@@ -321,3 +321,20 @@ export function isOutboundMessage(value: unknown): value is OutboundMessage {
|
|
|
321
321
|
&& isSendPolicy(candidate.policy)
|
|
322
322
|
&& typeof candidate.createdAt === 'number'
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
const PASTED_OPEN_RE = /^\s*<pasted_content(?:\s+id="[^"]*")?\s*>\s*$/
|
|
326
|
+
const PASTED_CLOSE_RE = /^\s*<\/pasted_content(?:\s+id="[^"]*")?\s*>\s*$/
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Drop the `<pasted_content>` envelope lines Claude Code wraps around a pasted
|
|
330
|
+
* input. Only a text that OPENS with the envelope is touched; anything else is
|
|
331
|
+
* returned unchanged. Shared across daemon transcript merge and web echo/display
|
|
332
|
+
* normalization so wrapped turns correlate identically.
|
|
333
|
+
*/
|
|
334
|
+
export function unwrapPastedContent(text: string): string {
|
|
335
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n')
|
|
336
|
+
const first = lines.findIndex((line) => line.trim())
|
|
337
|
+
if (first < 0 || !PASTED_OPEN_RE.test(lines[first])) return text
|
|
338
|
+
return lines.filter((line) => !PASTED_OPEN_RE.test(line) && !PASTED_CLOSE_RE.test(line)).join('\n')
|
|
339
|
+
}
|
|
340
|
+
|
package/src/path-ownership.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
/** One normalized ownership entry: a repo-relative path, optionally a `**` subtree prefix. */
|
|
36
36
|
export interface OwnedPathEntry {
|
|
37
|
-
/** Repo-relative path with forward slashes, no leading `./`, no trailing slash. */
|
|
37
|
+
/** Repo-relative path with forward slashes, no leading `./`, no trailing slash; empty for the whole-repo subtree. */
|
|
38
38
|
readonly path: string
|
|
39
39
|
/** True when this entry was declared as `<path>/**` (owns the whole subtree). */
|
|
40
40
|
readonly subtree: boolean
|
|
@@ -159,16 +159,14 @@ export function normalizeOwnedPaths(input: unknown): NormalizeOwnedPathsResult {
|
|
|
159
159
|
/** True when `path` is exactly `base`, or (when `base` is a subtree entry) nested under it. */
|
|
160
160
|
function entryContains(base: OwnedPathEntry, path: string): boolean {
|
|
161
161
|
if (path === base.path) return true
|
|
162
|
-
|
|
162
|
+
// An empty subtree prefix is the repo root, so it contains every normalized path.
|
|
163
|
+
if (base.subtree && (base.path === '' || path.startsWith(`${base.path}/`))) return true
|
|
163
164
|
return false
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
/** True when two normalized entries' claimed sets intersect (exact match, or one contains the other's directory). */
|
|
167
168
|
function entriesOverlap(a: OwnedPathEntry, b: OwnedPathEntry): boolean {
|
|
168
|
-
|
|
169
|
-
if (a.subtree && (b.path === a.path || b.path.startsWith(`${a.path}/`))) return true
|
|
170
|
-
if (b.subtree && (a.path === b.path || a.path.startsWith(`${b.path}/`))) return true
|
|
171
|
-
return false
|
|
169
|
+
return entryContains(a, b.path) || entryContains(b, a.path)
|
|
172
170
|
}
|
|
173
171
|
|
|
174
172
|
/** True when two declarations claim any overlapping path. Pure set comparison — no policy (refuse vs warn) here. */
|
|
@@ -73,6 +73,9 @@ export type DaemonStatusEventWirePayload = {
|
|
|
73
73
|
elapsedSec?: number
|
|
74
74
|
modalMessage?: string
|
|
75
75
|
modalButtons?: string[]
|
|
76
|
+
modalKey?: string
|
|
77
|
+
approvalEvidence?: 'live' | 'cached' | 'unavailable'
|
|
78
|
+
waitKind?: 'external'
|
|
76
79
|
surfaceHidden?: boolean
|
|
77
80
|
muted?: boolean
|
|
78
81
|
}
|
|
@@ -260,8 +263,8 @@ export const DAEMON_P2P_SIGNAL_TYPES = ['p2p_offer', 'p2p_ice'] as const
|
|
|
260
263
|
export type DaemonP2PSignalType = typeof DAEMON_P2P_SIGNAL_TYPES[number]
|
|
261
264
|
export const isDaemonP2PSignalType = makeTypeGuard(DAEMON_P2P_SIGNAL_TYPES)
|
|
262
265
|
|
|
263
|
-
export type P2POfferWirePayload = { sdp: string; type: string; peerId: string }
|
|
264
|
-
export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string }
|
|
266
|
+
export type P2POfferWirePayload = { sdp: string; type: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
267
|
+
export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
265
268
|
|
|
266
269
|
/**
|
|
267
270
|
* A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
|
|
@@ -273,6 +276,8 @@ export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string
|
|
|
273
276
|
export type RelayedP2PSignalWirePayload = {
|
|
274
277
|
peerId?: string
|
|
275
278
|
daemonId?: string
|
|
279
|
+
attemptId?: number
|
|
280
|
+
attemptEpoch?: string
|
|
276
281
|
sharePermission?: string
|
|
277
282
|
shareTargetSessionId?: string
|
|
278
283
|
sdp?: string
|
|
@@ -152,6 +152,8 @@ export type SystemAnnouncementWirePayload = {
|
|
|
152
152
|
export type DashboardP2POfferWirePayload = {
|
|
153
153
|
daemonId: string
|
|
154
154
|
peerId?: string
|
|
155
|
+
attemptId?: number
|
|
156
|
+
attemptEpoch?: string
|
|
155
157
|
sdp: string
|
|
156
158
|
type: string
|
|
157
159
|
}
|
|
@@ -159,6 +161,8 @@ export type DashboardP2POfferWirePayload = {
|
|
|
159
161
|
export type DashboardP2PIceWirePayload = {
|
|
160
162
|
daemonId: string
|
|
161
163
|
peerId?: string
|
|
164
|
+
attemptId?: number
|
|
165
|
+
attemptEpoch?: string
|
|
162
166
|
candidate: string
|
|
163
167
|
mid?: string
|
|
164
168
|
sdpMid?: string | null
|
|
@@ -234,11 +238,13 @@ export const DASHBOARD_P2P_SIGNAL_TYPES = ['p2p_ready', 'p2p_answer', 'p2p_ice']
|
|
|
234
238
|
export type DashboardP2PSignalType = typeof DASHBOARD_P2P_SIGNAL_TYPES[number]
|
|
235
239
|
export const isDashboardP2PSignalType = makeTypeGuard(DASHBOARD_P2P_SIGNAL_TYPES)
|
|
236
240
|
|
|
237
|
-
export type DashboardP2PReadyWirePayload = { daemonId: string; peerId: string }
|
|
238
|
-
export type DashboardP2PAnswerWirePayload = { daemonId: string; peerId: string; sdp: string; type: string }
|
|
241
|
+
export type DashboardP2PReadyWirePayload = { daemonId: string; peerId: string; attemptId?: number; attemptEpoch?: string }
|
|
242
|
+
export type DashboardP2PAnswerWirePayload = { daemonId: string; peerId: string; sdp: string; type: string; attemptId?: number; attemptEpoch?: string }
|
|
239
243
|
export type DashboardP2PLocalIceWirePayload = {
|
|
240
244
|
daemonId: string
|
|
241
245
|
peerId: string
|
|
246
|
+
attemptId?: number
|
|
247
|
+
attemptEpoch?: string
|
|
242
248
|
candidate: string
|
|
243
249
|
sdpMid: string | null
|
|
244
250
|
sdpMLineIndex: number | null
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Provider permissions are runtime decisions, never owner write ratification. */
|
|
2
|
+
export interface ProviderApproval {
|
|
3
|
+
source: 'provider_modal' | 'legacy_provider' | 'status_only' | 'ledger_approval';
|
|
4
|
+
/** Exact Inbox actions or the existing session handler; never inferred consent. */
|
|
5
|
+
compatibility?: 'exact' | 'session_only' | 'unknown';
|
|
6
|
+
type: 'tool_permission' | 'provider_setup' | 'unknown';
|
|
7
|
+
evidence: 'live' | 'cached' | 'unavailable';
|
|
8
|
+
lifecycle: 'pending' | 'resolved';
|
|
9
|
+
modalKey: string | null;
|
|
10
|
+
createdAt: number | null;
|
|
11
|
+
resolvedAt: number | null;
|
|
12
|
+
description: string;
|
|
13
|
+
runtimeDirectory?: string;
|
|
14
|
+
displayedDirectory?: string | null;
|
|
15
|
+
buttons: string[];
|
|
16
|
+
actionable: boolean;
|
|
17
|
+
providerType?: string;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
nodeId?: string | null;
|
|
20
|
+
meshId?: string | null;
|
|
21
|
+
taskId?: string | null;
|
|
22
|
+
attemptId?: string | null;
|
|
23
|
+
generation?: number | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Exact resource identity required by the approvals-only action path. */
|
|
27
|
+
export interface ProviderApprovalTarget {
|
|
28
|
+
modalKey: string;
|
|
29
|
+
attemptId: string | null;
|
|
30
|
+
generation: number | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Confirmed authority for the new Inbox path. Legacy handlers retain their own checks. */
|
|
34
|
+
export function isActionableProviderApproval(approval: ProviderApproval | null | undefined): boolean {
|
|
35
|
+
return !!approval && approval.source === 'provider_modal' && approval.type === 'tool_permission'
|
|
36
|
+
&& approval.lifecycle === 'pending' && approval.evidence === 'live' && approval.actionable
|
|
37
|
+
&& !!approval.modalKey && Array.isArray(approval.buttons)
|
|
38
|
+
&& approval.buttons.some(button => typeof button === 'string' && !!button.trim());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** An explicit null suppresses the resource; only missing metadata uses compatibility. */
|
|
42
|
+
export function readProviderDecision(approval: ProviderApproval | null | undefined, waiting: boolean, hasModal = false): ProviderApproval | null {
|
|
43
|
+
if (approval !== undefined) return approval && ['provider_modal', 'legacy_provider', 'status_only', 'ledger_approval'].includes(approval.source) ? approval : null;
|
|
44
|
+
if (!waiting) return null;
|
|
45
|
+
return { source: hasModal ? 'legacy_provider' : 'status_only', type: 'unknown',
|
|
46
|
+
compatibility: hasModal ? 'session_only' : 'unknown', evidence: 'unavailable',
|
|
47
|
+
lifecycle: 'pending', modalKey: null, createdAt: null, resolvedAt: null,
|
|
48
|
+
description: '', buttons: [], actionable: false };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Host metadata is durable: identity and clocks only, never modal text or directories. */
|
|
52
|
+
export function providerApprovalSnapshot(approval: ProviderApproval | null | undefined): ProviderApproval | null {
|
|
53
|
+
if (!approval) return null;
|
|
54
|
+
const { source, type, lifecycle, modalKey, createdAt, resolvedAt, providerType, sessionId,
|
|
55
|
+
nodeId, meshId, taskId, attemptId, generation } = approval;
|
|
56
|
+
return { source, type, lifecycle, modalKey, createdAt, resolvedAt, providerType, sessionId,
|
|
57
|
+
nodeId, meshId, taskId, attemptId, generation, evidence: 'cached', actionable: false,
|
|
58
|
+
description: '', buttons: [] };
|
|
59
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Ephemeral provenance supplied by the owning daemon, never inferred from prompt text. */
|
|
2
|
+
export interface ProviderQuestionBinding {
|
|
3
|
+
transport: 'codex_rpc' | 'codex_async';
|
|
4
|
+
sessionId: string;
|
|
5
|
+
generation: string;
|
|
6
|
+
threadId: string;
|
|
7
|
+
turnId: string;
|
|
8
|
+
itemId: string;
|
|
9
|
+
requestId?: string | number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ProviderQuestionLifecycle = 'pending' | 'submitted' | 'resolved' | 'completed' | 'cancelled' | 'expired' | 'unknown';
|
|
13
|
+
|
|
14
|
+
export interface ProviderQuestionEpisode {
|
|
15
|
+
blocking?: boolean;
|
|
16
|
+
binding?: ProviderQuestionBinding;
|
|
17
|
+
lifecycle?: ProviderQuestionLifecycle;
|
|
18
|
+
/** Async dismiss is product-local; it is not a native tool cancellation. */
|
|
19
|
+
support?: 'request_bound' | 'product_local' | 'uncertain';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isProviderQuestionBinding(value: unknown): value is ProviderQuestionBinding {
|
|
23
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
24
|
+
const b = value as Record<string, unknown>;
|
|
25
|
+
if (b.transport !== 'codex_rpc' && b.transport !== 'codex_async') return false;
|
|
26
|
+
const keys = ['transport', 'sessionId', 'generation', 'threadId', 'turnId', 'itemId', ...(b.transport === 'codex_rpc' ? ['requestId'] : [])];
|
|
27
|
+
if (Reflect.ownKeys(b).some(key => typeof key !== 'string' || !keys.includes(key))) return false;
|
|
28
|
+
if (['sessionId', 'generation', 'threadId', 'turnId', 'itemId'].some(key => typeof b[key] !== 'string' || !(b[key] as string).trim())) return false;
|
|
29
|
+
return b.transport === 'codex_async' ? b.requestId === undefined
|
|
30
|
+
: (typeof b.requestId === 'string' && !!b.requestId.trim()) || (typeof b.requestId === 'number' && Number.isFinite(b.requestId));
|
|
31
|
+
}
|