@tangle-network/agent-app 0.44.57 → 0.44.59
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/index.d.ts +1 -1
- package/dist/{chunk-OO4DROKM.js → chunk-DMYCGXBP.js} +1 -1
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/vault/server.d.ts +130 -0
- package/dist/vault/server.js +60 -0
- package/dist/vault/server.js.map +1 -0
- package/dist/web-react/index.d.ts +1 -1
- package/dist/web-react/terminal.d.ts +46 -44
- package/dist/web-react/terminal.js +0 -86
- package/dist/web-react/terminal.js.map +1 -1
- package/package.json +6 -1
- package/dist/sandbox-terminal-ChNEdHF8.d.ts +0 -51
- /package/dist/{chunk-OO4DROKM.js.map → chunk-DMYCGXBP.js.map} +0 -0
|
@@ -11,7 +11,7 @@ import '../use-file-mentions-CZ-Ua_sb.js';
|
|
|
11
11
|
import '../agent-activity-C8ZG0F0M.js';
|
|
12
12
|
import '../flow-types-CJxEmaRy.js';
|
|
13
13
|
import '../queue-VTBA5ONX.js';
|
|
14
|
-
import '../
|
|
14
|
+
import '../web-react/terminal.js';
|
|
15
15
|
import '../billing-BibxgALe.js';
|
|
16
16
|
import '../billing/index.js';
|
|
17
17
|
import '../session-shell/index.js';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only policy sibling of the React `./vault` client surface. Pure
|
|
3
|
+
* decision logic — no I/O, no logging, ZERO imports — extracted from
|
|
4
|
+
* gtm-agent's local guard (gtm#612) after it lost 48 production vault files:
|
|
5
|
+
* a half-mounted sandbox scanned cleanly and reconciliation derived a
|
|
6
|
+
* mass-deletion batch from an empty manifest. The #265/#332 audit ruled both
|
|
7
|
+
* checks here universal shell mechanism, not gtm-specific, so they live here
|
|
8
|
+
* once instead of being re-forked per product.
|
|
9
|
+
*
|
|
10
|
+
* Two independent checks:
|
|
11
|
+
* - `assessVaultDeletionBatch` — blast-radius refusal. Given the live
|
|
12
|
+
* baseline and what a reconciliation pass proposes to delete, decide
|
|
13
|
+
* whether the batch is safe to apply or must be refused. Refusal is a
|
|
14
|
+
* SUCCESSFUL outcome, not a thrown error: content changes still apply,
|
|
15
|
+
* only the deletions are withheld. What to do with a refusal (log, alert,
|
|
16
|
+
* retry the scan) stays the caller's job.
|
|
17
|
+
* - `compareIncarnationBaseline` — filesystem-incarnation comparison. Given
|
|
18
|
+
* a recorded baseline incarnation id and the sandbox's current identity
|
|
19
|
+
* fields, decide whether the sandbox filesystem is the same one the
|
|
20
|
+
* baseline was captured against, fail-closed on anything not clearly
|
|
21
|
+
* identified and ready.
|
|
22
|
+
*
|
|
23
|
+
* Both are pure functions over caller-supplied data: no manifest reads, no
|
|
24
|
+
* sandbox calls, no console output. The caller pre-filters tombstoned
|
|
25
|
+
* entries (this module only ever sees the LIVE path set) and owns everything
|
|
26
|
+
* that happens after a verdict comes back.
|
|
27
|
+
*/
|
|
28
|
+
/** Refuse a deletion batch once it would remove at least this fraction of the
|
|
29
|
+
* live baseline (both this and `VAULT_DELETION_REFUSAL_MIN_LIVE_FILES` must
|
|
30
|
+
* hold — a tiny vault can lose all its files without tripping this ratio
|
|
31
|
+
* gate; `refusesAllFiles` catches that case separately). */
|
|
32
|
+
declare const VAULT_DELETION_REFUSAL_RATIO = 0.75;
|
|
33
|
+
/** The blast-radius ratio gate only engages once the live baseline is at
|
|
34
|
+
* least this large, so a 2-file vault losing 1 file (50%) is not refused on
|
|
35
|
+
* ratio alone. */
|
|
36
|
+
declare const VAULT_DELETION_REFUSAL_MIN_LIVE_FILES = 10;
|
|
37
|
+
/** Overrides for `assessVaultDeletionBatch`'s two thresholds. Both default to
|
|
38
|
+
* the exported constants above. Values are validated fail-loud:
|
|
39
|
+
* `refusalRatio` must be finite and in (0, 1] — `NaN` (a config parse
|
|
40
|
+
* failure) would make `deletionRatio >= refusalRatio` always false and silently
|
|
41
|
+
* disable the guard, and `0` would refuse a zero-deletion batch; and
|
|
42
|
+
* `minLiveFiles` must be finite and >= 0. Invalid values throw `RangeError`
|
|
43
|
+
* instead of running with a guard that cannot fire (or cannot pass). */
|
|
44
|
+
interface VaultDeletionPolicy {
|
|
45
|
+
refusalRatio?: number;
|
|
46
|
+
minLiveFiles?: number;
|
|
47
|
+
}
|
|
48
|
+
interface VaultDeletionAssessment {
|
|
49
|
+
/** True when no refusal reason fired. */
|
|
50
|
+
allowed: boolean;
|
|
51
|
+
/** Precedence when multiple reasons would fire: `empty-manifest` beats
|
|
52
|
+
* `all-files` beats `ratio-exceeded` (most specific first — an empty scan
|
|
53
|
+
* IS an all-files deletion, but the diagnostic that matters is "the scan
|
|
54
|
+
* came back empty", not "it deleted everything"). Undefined when allowed. */
|
|
55
|
+
reason?: 'empty-manifest' | 'all-files' | 'ratio-exceeded';
|
|
56
|
+
/** wouldDelete / baselineLive, or 0 when the baseline is itself empty. */
|
|
57
|
+
deletionRatio: number;
|
|
58
|
+
wouldDelete: number;
|
|
59
|
+
/** The paths that would be deleted, sorted — mirrors gtm's `.sort()` so log
|
|
60
|
+
* output and any snapshot fixture are byte-stable. */
|
|
61
|
+
wouldDeletePaths: string[];
|
|
62
|
+
baselineLive: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Decide whether a reconciliation pass's proposed deletions are safe to
|
|
66
|
+
* apply against the live baseline.
|
|
67
|
+
*
|
|
68
|
+
* `manifestEmpty: true` means the filesystem scan that produced
|
|
69
|
+
* `proposedDeletions` came back with nothing — the caller could not have
|
|
70
|
+
* derived a meaningful proposed-deletions list, so this treats the FULL live
|
|
71
|
+
* baseline as would-delete regardless of what `proposedDeletions` contains.
|
|
72
|
+
* That is what distinguishes `'empty-manifest'` from `'all-files'`: an empty
|
|
73
|
+
* scan structurally implies every baseline path would be deleted, but the
|
|
74
|
+
* diagnostic that matters to a caller is "the scan was empty", not merely
|
|
75
|
+
* "everything would go".
|
|
76
|
+
*/
|
|
77
|
+
declare function assessVaultDeletionBatch(input: {
|
|
78
|
+
/** LIVE (non-tombstoned) baseline path set — the caller pre-filters, as gtm does. */
|
|
79
|
+
baselinePaths: readonly string[];
|
|
80
|
+
/** Paths a reconciliation pass proposes to delete; intersected with `baselinePaths` internally. */
|
|
81
|
+
proposedDeletions: readonly string[];
|
|
82
|
+
manifestEmpty?: boolean;
|
|
83
|
+
policy?: VaultDeletionPolicy;
|
|
84
|
+
}): VaultDeletionAssessment;
|
|
85
|
+
/** The subset of a sandbox's filesystem-incarnation identity this module
|
|
86
|
+
* needs. A product's real incarnation type is structurally compatible —
|
|
87
|
+
* no import required. */
|
|
88
|
+
interface FilesystemIncarnationLike {
|
|
89
|
+
filesystemIncarnationId?: string;
|
|
90
|
+
filesystemIncarnationProvenance?: 'fresh' | 'restored' | 'unknown';
|
|
91
|
+
filesystemIncarnationReadiness?: 'transitioning' | 'ready';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Result of comparing a recorded baseline incarnation id against the
|
|
95
|
+
* sandbox's current identity. A discriminated union over `verdict` so a
|
|
96
|
+
* caller's `switch` gets compile-time coverage instead of a stringly-typed
|
|
97
|
+
* status it can forget to branch on.
|
|
98
|
+
*/
|
|
99
|
+
type IncarnationComparison = {
|
|
100
|
+
verdict: 'match';
|
|
101
|
+
} | {
|
|
102
|
+
verdict: 'mismatch';
|
|
103
|
+
baselineId: string;
|
|
104
|
+
currentId: string;
|
|
105
|
+
} | {
|
|
106
|
+
verdict: 'not-ready';
|
|
107
|
+
readiness: string | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
verdict: 'unidentified';
|
|
110
|
+
} | {
|
|
111
|
+
verdict: 'no-baseline';
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Compare a recorded baseline filesystem-incarnation id against a sandbox's
|
|
115
|
+
* current incarnation fields. Check order mirrors gtm's gate sequence and is
|
|
116
|
+
* itself a tested fail-closed property — each earlier check short-circuits
|
|
117
|
+
* the ones after it, so a box with BOTH bad readiness and a mismatched id
|
|
118
|
+
* resolves `'not-ready'`, never `'mismatch'`:
|
|
119
|
+
*
|
|
120
|
+
* 1. `unidentified` — the current id is missing/empty, or the provenance is
|
|
121
|
+
* not one of `fresh`/`restored`/`unknown`.
|
|
122
|
+
* 2. `not-ready` — readiness is anything other than the literal string
|
|
123
|
+
* `'ready'` (this also catches `undefined`).
|
|
124
|
+
* 3. `no-baseline` — no baseline id was ever recorded to compare against.
|
|
125
|
+
* 4. `mismatch` — the baseline id and current id disagree.
|
|
126
|
+
* 5. `match` — same incarnation.
|
|
127
|
+
*/
|
|
128
|
+
declare function compareIncarnationBaseline(baselineId: string | undefined, current: FilesystemIncarnationLike): IncarnationComparison;
|
|
129
|
+
|
|
130
|
+
export { type FilesystemIncarnationLike, type IncarnationComparison, VAULT_DELETION_REFUSAL_MIN_LIVE_FILES, VAULT_DELETION_REFUSAL_RATIO, type VaultDeletionAssessment, type VaultDeletionPolicy, assessVaultDeletionBatch, compareIncarnationBaseline };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/vault/server.ts
|
|
2
|
+
var VAULT_DELETION_REFUSAL_RATIO = 0.75;
|
|
3
|
+
var VAULT_DELETION_REFUSAL_MIN_LIVE_FILES = 10;
|
|
4
|
+
function assessVaultDeletionBatch(input) {
|
|
5
|
+
const refusalRatio = input.policy?.refusalRatio ?? VAULT_DELETION_REFUSAL_RATIO;
|
|
6
|
+
const minLiveFiles = input.policy?.minLiveFiles ?? VAULT_DELETION_REFUSAL_MIN_LIVE_FILES;
|
|
7
|
+
if (!Number.isFinite(refusalRatio) || refusalRatio <= 0 || refusalRatio > 1) {
|
|
8
|
+
throw new RangeError(
|
|
9
|
+
`vault deletion policy refusalRatio must be a finite number in (0, 1], got ${refusalRatio}`
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
if (!Number.isFinite(minLiveFiles) || minLiveFiles < 0) {
|
|
13
|
+
throw new RangeError(
|
|
14
|
+
`vault deletion policy minLiveFiles must be a finite number >= 0, got ${minLiveFiles}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const manifestEmpty = input.manifestEmpty ?? false;
|
|
18
|
+
const baselineLive = input.baselinePaths.length;
|
|
19
|
+
const proposedSet = new Set(input.proposedDeletions);
|
|
20
|
+
const wouldDeletePaths = manifestEmpty ? [...input.baselinePaths].sort() : input.baselinePaths.filter((p) => proposedSet.has(p)).sort();
|
|
21
|
+
const wouldDelete = wouldDeletePaths.length;
|
|
22
|
+
const deletionRatio = baselineLive > 0 ? wouldDelete / baselineLive : 0;
|
|
23
|
+
const refusesEmptyManifest = manifestEmpty && baselineLive > 0;
|
|
24
|
+
const refusesAllFiles = wouldDelete > 0 && wouldDelete === baselineLive;
|
|
25
|
+
const refusesBlastRadius = wouldDelete > 0 && baselineLive >= minLiveFiles && deletionRatio >= refusalRatio;
|
|
26
|
+
const reason = refusesEmptyManifest ? "empty-manifest" : refusesAllFiles ? "all-files" : refusesBlastRadius ? "ratio-exceeded" : void 0;
|
|
27
|
+
return {
|
|
28
|
+
allowed: reason === void 0,
|
|
29
|
+
reason,
|
|
30
|
+
deletionRatio,
|
|
31
|
+
wouldDelete,
|
|
32
|
+
wouldDeletePaths,
|
|
33
|
+
baselineLive
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function compareIncarnationBaseline(baselineId, current) {
|
|
37
|
+
const currentId = current.filesystemIncarnationId;
|
|
38
|
+
const provenance = current.filesystemIncarnationProvenance;
|
|
39
|
+
const isValidProvenance = provenance === "fresh" || provenance === "restored" || provenance === "unknown";
|
|
40
|
+
if (typeof currentId !== "string" || currentId.length === 0 || !isValidProvenance) {
|
|
41
|
+
return { verdict: "unidentified" };
|
|
42
|
+
}
|
|
43
|
+
if (current.filesystemIncarnationReadiness !== "ready") {
|
|
44
|
+
return { verdict: "not-ready", readiness: current.filesystemIncarnationReadiness };
|
|
45
|
+
}
|
|
46
|
+
if (baselineId === void 0) {
|
|
47
|
+
return { verdict: "no-baseline" };
|
|
48
|
+
}
|
|
49
|
+
if (baselineId !== currentId) {
|
|
50
|
+
return { verdict: "mismatch", baselineId, currentId };
|
|
51
|
+
}
|
|
52
|
+
return { verdict: "match" };
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
VAULT_DELETION_REFUSAL_MIN_LIVE_FILES,
|
|
56
|
+
VAULT_DELETION_REFUSAL_RATIO,
|
|
57
|
+
assessVaultDeletionBatch,
|
|
58
|
+
compareIncarnationBaseline
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/vault/server.ts"],"sourcesContent":["/**\n * Server-only policy sibling of the React `./vault` client surface. Pure\n * decision logic — no I/O, no logging, ZERO imports — extracted from\n * gtm-agent's local guard (gtm#612) after it lost 48 production vault files:\n * a half-mounted sandbox scanned cleanly and reconciliation derived a\n * mass-deletion batch from an empty manifest. The #265/#332 audit ruled both\n * checks here universal shell mechanism, not gtm-specific, so they live here\n * once instead of being re-forked per product.\n *\n * Two independent checks:\n * - `assessVaultDeletionBatch` — blast-radius refusal. Given the live\n * baseline and what a reconciliation pass proposes to delete, decide\n * whether the batch is safe to apply or must be refused. Refusal is a\n * SUCCESSFUL outcome, not a thrown error: content changes still apply,\n * only the deletions are withheld. What to do with a refusal (log, alert,\n * retry the scan) stays the caller's job.\n * - `compareIncarnationBaseline` — filesystem-incarnation comparison. Given\n * a recorded baseline incarnation id and the sandbox's current identity\n * fields, decide whether the sandbox filesystem is the same one the\n * baseline was captured against, fail-closed on anything not clearly\n * identified and ready.\n *\n * Both are pure functions over caller-supplied data: no manifest reads, no\n * sandbox calls, no console output. The caller pre-filters tombstoned\n * entries (this module only ever sees the LIVE path set) and owns everything\n * that happens after a verdict comes back.\n */\n\n/** Refuse a deletion batch once it would remove at least this fraction of the\n * live baseline (both this and `VAULT_DELETION_REFUSAL_MIN_LIVE_FILES` must\n * hold — a tiny vault can lose all its files without tripping this ratio\n * gate; `refusesAllFiles` catches that case separately). */\nexport const VAULT_DELETION_REFUSAL_RATIO = 0.75\n\n/** The blast-radius ratio gate only engages once the live baseline is at\n * least this large, so a 2-file vault losing 1 file (50%) is not refused on\n * ratio alone. */\nexport const VAULT_DELETION_REFUSAL_MIN_LIVE_FILES = 10\n\n/** Overrides for `assessVaultDeletionBatch`'s two thresholds. Both default to\n * the exported constants above. Values are validated fail-loud:\n * `refusalRatio` must be finite and in (0, 1] — `NaN` (a config parse\n * failure) would make `deletionRatio >= refusalRatio` always false and silently\n * disable the guard, and `0` would refuse a zero-deletion batch; and\n * `minLiveFiles` must be finite and >= 0. Invalid values throw `RangeError`\n * instead of running with a guard that cannot fire (or cannot pass). */\nexport interface VaultDeletionPolicy {\n refusalRatio?: number\n minLiveFiles?: number\n}\n\nexport interface VaultDeletionAssessment {\n /** True when no refusal reason fired. */\n allowed: boolean\n /** Precedence when multiple reasons would fire: `empty-manifest` beats\n * `all-files` beats `ratio-exceeded` (most specific first — an empty scan\n * IS an all-files deletion, but the diagnostic that matters is \"the scan\n * came back empty\", not \"it deleted everything\"). Undefined when allowed. */\n reason?: 'empty-manifest' | 'all-files' | 'ratio-exceeded'\n /** wouldDelete / baselineLive, or 0 when the baseline is itself empty. */\n deletionRatio: number\n wouldDelete: number\n /** The paths that would be deleted, sorted — mirrors gtm's `.sort()` so log\n * output and any snapshot fixture are byte-stable. */\n wouldDeletePaths: string[]\n baselineLive: number\n}\n\n/**\n * Decide whether a reconciliation pass's proposed deletions are safe to\n * apply against the live baseline.\n *\n * `manifestEmpty: true` means the filesystem scan that produced\n * `proposedDeletions` came back with nothing — the caller could not have\n * derived a meaningful proposed-deletions list, so this treats the FULL live\n * baseline as would-delete regardless of what `proposedDeletions` contains.\n * That is what distinguishes `'empty-manifest'` from `'all-files'`: an empty\n * scan structurally implies every baseline path would be deleted, but the\n * diagnostic that matters to a caller is \"the scan was empty\", not merely\n * \"everything would go\".\n */\nexport function assessVaultDeletionBatch(input: {\n /** LIVE (non-tombstoned) baseline path set — the caller pre-filters, as gtm does. */\n baselinePaths: readonly string[]\n /** Paths a reconciliation pass proposes to delete; intersected with `baselinePaths` internally. */\n proposedDeletions: readonly string[]\n manifestEmpty?: boolean\n policy?: VaultDeletionPolicy\n}): VaultDeletionAssessment {\n const refusalRatio = input.policy?.refusalRatio ?? VAULT_DELETION_REFUSAL_RATIO\n const minLiveFiles = input.policy?.minLiveFiles ?? VAULT_DELETION_REFUSAL_MIN_LIVE_FILES\n if (!Number.isFinite(refusalRatio) || refusalRatio <= 0 || refusalRatio > 1) {\n throw new RangeError(\n `vault deletion policy refusalRatio must be a finite number in (0, 1], got ${refusalRatio}`,\n )\n }\n if (!Number.isFinite(minLiveFiles) || minLiveFiles < 0) {\n throw new RangeError(\n `vault deletion policy minLiveFiles must be a finite number >= 0, got ${minLiveFiles}`,\n )\n }\n const manifestEmpty = input.manifestEmpty ?? false\n\n const baselineLive = input.baselinePaths.length\n const proposedSet = new Set(input.proposedDeletions)\n const wouldDeletePaths = manifestEmpty\n ? [...input.baselinePaths].sort()\n : input.baselinePaths.filter((p) => proposedSet.has(p)).sort()\n const wouldDelete = wouldDeletePaths.length\n const deletionRatio = baselineLive > 0 ? wouldDelete / baselineLive : 0\n\n const refusesEmptyManifest = manifestEmpty && baselineLive > 0\n const refusesAllFiles = wouldDelete > 0 && wouldDelete === baselineLive\n // `wouldDelete > 0` is unreachable-false under the validated policy domain\n // (a positive refusalRatio can never be met by a 0 ratio) — it is here so\n // the ratio branch stays safe on its own terms, independent of the\n // validation above ever being relaxed.\n const refusesBlastRadius = wouldDelete > 0\n && baselineLive >= minLiveFiles\n && deletionRatio >= refusalRatio\n\n const reason = refusesEmptyManifest\n ? ('empty-manifest' as const)\n : refusesAllFiles\n ? ('all-files' as const)\n : refusesBlastRadius\n ? ('ratio-exceeded' as const)\n : undefined\n\n return {\n allowed: reason === undefined,\n reason,\n deletionRatio,\n wouldDelete,\n wouldDeletePaths,\n baselineLive,\n }\n}\n\n/** The subset of a sandbox's filesystem-incarnation identity this module\n * needs. A product's real incarnation type is structurally compatible —\n * no import required. */\nexport interface FilesystemIncarnationLike {\n filesystemIncarnationId?: string\n filesystemIncarnationProvenance?: 'fresh' | 'restored' | 'unknown'\n filesystemIncarnationReadiness?: 'transitioning' | 'ready'\n}\n\n/**\n * Result of comparing a recorded baseline incarnation id against the\n * sandbox's current identity. A discriminated union over `verdict` so a\n * caller's `switch` gets compile-time coverage instead of a stringly-typed\n * status it can forget to branch on.\n */\nexport type IncarnationComparison =\n | { verdict: 'match' }\n | { verdict: 'mismatch'; baselineId: string; currentId: string }\n | { verdict: 'not-ready'; readiness: string | undefined }\n | { verdict: 'unidentified' }\n | { verdict: 'no-baseline' }\n\n/**\n * Compare a recorded baseline filesystem-incarnation id against a sandbox's\n * current incarnation fields. Check order mirrors gtm's gate sequence and is\n * itself a tested fail-closed property — each earlier check short-circuits\n * the ones after it, so a box with BOTH bad readiness and a mismatched id\n * resolves `'not-ready'`, never `'mismatch'`:\n *\n * 1. `unidentified` — the current id is missing/empty, or the provenance is\n * not one of `fresh`/`restored`/`unknown`.\n * 2. `not-ready` — readiness is anything other than the literal string\n * `'ready'` (this also catches `undefined`).\n * 3. `no-baseline` — no baseline id was ever recorded to compare against.\n * 4. `mismatch` — the baseline id and current id disagree.\n * 5. `match` — same incarnation.\n */\nexport function compareIncarnationBaseline(\n baselineId: string | undefined,\n current: FilesystemIncarnationLike,\n): IncarnationComparison {\n const currentId = current.filesystemIncarnationId\n const provenance = current.filesystemIncarnationProvenance\n const isValidProvenance = provenance === 'fresh' || provenance === 'restored' || provenance === 'unknown'\n\n if (typeof currentId !== 'string' || currentId.length === 0 || !isValidProvenance) {\n return { verdict: 'unidentified' }\n }\n\n if (current.filesystemIncarnationReadiness !== 'ready') {\n return { verdict: 'not-ready', readiness: current.filesystemIncarnationReadiness }\n }\n\n if (baselineId === undefined) {\n return { verdict: 'no-baseline' }\n }\n\n if (baselineId !== currentId) {\n return { verdict: 'mismatch', baselineId, currentId }\n }\n\n return { verdict: 'match' }\n}\n"],"mappings":";AAgCO,IAAM,+BAA+B;AAKrC,IAAM,wCAAwC;AA4C9C,SAAS,yBAAyB,OAOb;AAC1B,QAAM,eAAe,MAAM,QAAQ,gBAAgB;AACnD,QAAM,eAAe,MAAM,QAAQ,gBAAgB;AACnD,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,gBAAgB,KAAK,eAAe,GAAG;AAC3E,UAAM,IAAI;AAAA,MACR,6EAA6E,YAAY;AAAA,IAC3F;AAAA,EACF;AACA,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,eAAe,GAAG;AACtD,UAAM,IAAI;AAAA,MACR,wEAAwE,YAAY;AAAA,IACtF;AAAA,EACF;AACA,QAAM,gBAAgB,MAAM,iBAAiB;AAE7C,QAAM,eAAe,MAAM,cAAc;AACzC,QAAM,cAAc,IAAI,IAAI,MAAM,iBAAiB;AACnD,QAAM,mBAAmB,gBACrB,CAAC,GAAG,MAAM,aAAa,EAAE,KAAK,IAC9B,MAAM,cAAc,OAAO,CAAC,MAAM,YAAY,IAAI,CAAC,CAAC,EAAE,KAAK;AAC/D,QAAM,cAAc,iBAAiB;AACrC,QAAM,gBAAgB,eAAe,IAAI,cAAc,eAAe;AAEtE,QAAM,uBAAuB,iBAAiB,eAAe;AAC7D,QAAM,kBAAkB,cAAc,KAAK,gBAAgB;AAK3D,QAAM,qBAAqB,cAAc,KACpC,gBAAgB,gBAChB,iBAAiB;AAEtB,QAAM,SAAS,uBACV,mBACD,kBACG,cACD,qBACG,mBACD;AAER,SAAO;AAAA,IACL,SAAS,WAAW;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAuCO,SAAS,2BACd,YACA,SACuB;AACvB,QAAM,YAAY,QAAQ;AAC1B,QAAM,aAAa,QAAQ;AAC3B,QAAM,oBAAoB,eAAe,WAAW,eAAe,cAAc,eAAe;AAEhG,MAAI,OAAO,cAAc,YAAY,UAAU,WAAW,KAAK,CAAC,mBAAmB;AACjF,WAAO,EAAE,SAAS,eAAe;AAAA,EACnC;AAEA,MAAI,QAAQ,mCAAmC,SAAS;AACtD,WAAO,EAAE,SAAS,aAAa,WAAW,QAAQ,+BAA+B;AAAA,EACnF;AAEA,MAAI,eAAe,QAAW;AAC5B,WAAO,EAAE,SAAS,cAAc;AAAA,EAClC;AAEA,MAAI,eAAe,WAAW;AAC5B,WAAO,EAAE,SAAS,YAAY,YAAY,UAAU;AAAA,EACtD;AAEA,SAAO,EAAE,SAAS,QAAQ;AAC5B;","names":[]}
|
|
@@ -15,7 +15,7 @@ import { S as StepAgentActivity } from '../agent-activity-C8ZG0F0M.js';
|
|
|
15
15
|
import { F as FlowTrace } from '../flow-types-CJxEmaRy.js';
|
|
16
16
|
import { a as ReviewQueueItem, c as ReviewQueueState } from '../queue-VTBA5ONX.js';
|
|
17
17
|
export { p as parseReviewQueueItem } from '../queue-VTBA5ONX.js';
|
|
18
|
-
export {
|
|
18
|
+
export { SandboxTerminalConnection, SandboxTerminalConnectionResponse, UseSandboxTerminalConnectionOptions, UseSandboxTerminalConnectionResult, tabTerminalConnectionId, useSandboxTerminalConnection } from './terminal.js';
|
|
19
19
|
import { i as ProductSeatOffer } from '../billing-BibxgALe.js';
|
|
20
20
|
import { SessionSort, SessionPage, SessionSummary, SessionRailAction } from '../session-shell/index.js';
|
|
21
21
|
import { CatalogModel } from '../catalog/index.js';
|
|
@@ -1,49 +1,51 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/** Define the connection details and status for a sandbox terminal session */
|
|
2
|
+
interface SandboxTerminalConnection {
|
|
3
|
+
runtimeUrl: string | null;
|
|
4
|
+
sidecarUrl: string | null;
|
|
5
|
+
token: string | null;
|
|
6
|
+
expiresAt: string | null;
|
|
7
|
+
status: string;
|
|
8
|
+
error: string | null;
|
|
9
|
+
loading: boolean;
|
|
10
|
+
sandboxId?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Define the response structure for a sandbox terminal connection including URLs, token, status, and errors */
|
|
13
|
+
interface SandboxTerminalConnectionResponse {
|
|
14
|
+
runtimeUrl?: string;
|
|
15
|
+
sidecarUrl?: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
expiresAt?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
sandboxId?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Define options for configuring a sandbox terminal connection including workspace ID and connection parameters */
|
|
23
|
+
interface UseSandboxTerminalConnectionOptions {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
connectionUrl?: string | ((workspaceId: string) => string);
|
|
26
|
+
fetcher?: typeof fetch;
|
|
27
|
+
provisionPollIntervalMs?: number;
|
|
28
|
+
provisionPollTimeoutMs?: number;
|
|
29
|
+
tokenRefreshSkewMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Resolve sandbox terminal connection status and provide a method to initiate the connection */
|
|
32
|
+
interface UseSandboxTerminalConnectionResult extends SandboxTerminalConnection {
|
|
33
|
+
connect: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** Manage and maintain a sandbox terminal connection with automatic polling and token refresh handling */
|
|
36
|
+
declare function useSandboxTerminalConnection(opts: UseSandboxTerminalConnectionOptions): UseSandboxTerminalConnectionResult;
|
|
5
37
|
/**
|
|
6
|
-
*
|
|
7
|
-
* a status badge, connect/provisioning/error states with a retry, and the lazy
|
|
8
|
-
* `TerminalView` (from `@tangle-network/sandbox-ui`) mounted only once the
|
|
9
|
-
* connection is live. creative-agent and gtm-agent each hand-roll a structurally
|
|
10
|
-
* identical panel; only the copy and the status-tone map are app-specific, so
|
|
11
|
-
* those are props and everything else lives here.
|
|
38
|
+
* Stable-per-tab, unique-per-client terminal connection id.
|
|
12
39
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
40
|
+
* Persists in `sessionStorage` so a reload in the same tab reuses the id (the
|
|
41
|
+
* sidecar restores the same PTY session via `TerminalView.connectionId`), while
|
|
42
|
+
* separate tabs/windows each get a distinct id. Pass the result as
|
|
43
|
+
* `TerminalView`'s `connectionId`. Without it (e.g. gtm-agent today) every tab
|
|
44
|
+
* shares one connection id and their reconnects evict each other.
|
|
16
45
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
46
|
+
* Falls back to an ephemeral id when `sessionStorage` is unavailable (SSR,
|
|
47
|
+
* privacy mode) — still unique per call, just not reload-stable.
|
|
19
48
|
*/
|
|
49
|
+
declare function tabTerminalConnectionId(storageKey?: string): string;
|
|
20
50
|
|
|
21
|
-
type
|
|
22
|
-
interface TerminalStatusDisplay {
|
|
23
|
-
tone: TerminalStatusTone;
|
|
24
|
-
label: string;
|
|
25
|
-
}
|
|
26
|
-
interface WorkspaceTerminalPanelProps {
|
|
27
|
-
/** Live connection state (from {@link useSandboxTerminalConnection}). */
|
|
28
|
-
connection: SandboxTerminalConnection;
|
|
29
|
-
/** Stable per-tab id (from {@link tabTerminalConnectionId}) so the sidecar
|
|
30
|
-
* restores the same PTY across remounts and tabs don't collide. */
|
|
31
|
-
connectionId?: string;
|
|
32
|
-
/** Header title. Default "Terminal". */
|
|
33
|
-
title?: string;
|
|
34
|
-
/** Header subtitle / sandbox label. */
|
|
35
|
-
subtitle?: string;
|
|
36
|
-
/** Whether the terminal tab is visible (forwarded to `TerminalView` for fit). */
|
|
37
|
-
isActive?: boolean;
|
|
38
|
-
/** Reconnect handler — wire to the hook's `connect`. Shown on idle/error. */
|
|
39
|
-
onRetry?: () => void;
|
|
40
|
-
/** Map a `connection.status` to a badge tone + label. The default covers
|
|
41
|
-
* idle/provisioning/running/error; override for app-specific vocabulary. */
|
|
42
|
-
statusDisplay?: (connection: SandboxTerminalConnection) => TerminalStatusDisplay;
|
|
43
|
-
/** Extra header content, right-aligned (actions, sandbox id, …). */
|
|
44
|
-
headerExtra?: ReactNode;
|
|
45
|
-
className?: string;
|
|
46
|
-
}
|
|
47
|
-
declare function WorkspaceTerminalPanel({ connection, connectionId, title, subtitle, isActive, onRetry, statusDisplay, headerExtra, className, }: WorkspaceTerminalPanelProps): ReactNode;
|
|
48
|
-
|
|
49
|
-
export { SandboxTerminalConnection, type TerminalStatusDisplay, type TerminalStatusTone, WorkspaceTerminalPanel, type WorkspaceTerminalPanelProps };
|
|
51
|
+
export { type SandboxTerminalConnection, type SandboxTerminalConnectionResponse, type UseSandboxTerminalConnectionOptions, type UseSandboxTerminalConnectionResult, tabTerminalConnectionId, useSandboxTerminalConnection };
|
|
@@ -2,93 +2,7 @@ import {
|
|
|
2
2
|
tabTerminalConnectionId,
|
|
3
3
|
useSandboxTerminalConnection
|
|
4
4
|
} from "../chunk-HCOROIRT.js";
|
|
5
|
-
|
|
6
|
-
// src/web-react/workspace-terminal-panel.tsx
|
|
7
|
-
import { lazy, Suspense } from "react";
|
|
8
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
-
var TerminalView = lazy(
|
|
10
|
-
() => import("@tangle-network/sandbox-ui/terminal").then((m) => ({ default: m.TerminalView }))
|
|
11
|
-
);
|
|
12
|
-
var TONE_DOT = {
|
|
13
|
-
idle: "bg-muted-foreground/50",
|
|
14
|
-
connecting: "animate-pulse bg-warning",
|
|
15
|
-
connected: "bg-success",
|
|
16
|
-
error: "bg-destructive"
|
|
17
|
-
};
|
|
18
|
-
function defaultStatusDisplay(conn) {
|
|
19
|
-
if (conn.error) return { tone: "error", label: "Disconnected" };
|
|
20
|
-
if (conn.runtimeUrl && conn.token) return { tone: "connected", label: "Connected" };
|
|
21
|
-
if (conn.loading) return { tone: "connecting", label: conn.status === "provisioning" ? "Provisioning\u2026" : "Connecting\u2026" };
|
|
22
|
-
return { tone: "idle", label: "Idle" };
|
|
23
|
-
}
|
|
24
|
-
function WorkspaceTerminalPanel({
|
|
25
|
-
connection,
|
|
26
|
-
connectionId,
|
|
27
|
-
title = "Terminal",
|
|
28
|
-
subtitle,
|
|
29
|
-
isActive,
|
|
30
|
-
onRetry,
|
|
31
|
-
statusDisplay,
|
|
32
|
-
headerExtra,
|
|
33
|
-
className
|
|
34
|
-
}) {
|
|
35
|
-
const status = (statusDisplay ?? defaultStatusDisplay)(connection);
|
|
36
|
-
const apiUrl = connection.runtimeUrl ?? connection.sidecarUrl;
|
|
37
|
-
const ready = Boolean(apiUrl && connection.token);
|
|
38
|
-
return /* @__PURE__ */ jsxs("div", { className: `flex h-full min-h-0 flex-col overflow-hidden rounded-xl border border-border bg-card ${className ?? ""}`, children: [
|
|
39
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
|
|
40
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
41
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: title }),
|
|
42
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: subtitle })
|
|
43
|
-
] }),
|
|
44
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
45
|
-
/* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
46
|
-
/* @__PURE__ */ jsx("span", { className: `h-1.5 w-1.5 rounded-full ${TONE_DOT[status.tone]}`, "aria-hidden": true }),
|
|
47
|
-
status.label
|
|
48
|
-
] }),
|
|
49
|
-
headerExtra
|
|
50
|
-
] })
|
|
51
|
-
] }),
|
|
52
|
-
/* @__PURE__ */ jsx("div", { className: "relative min-h-0 flex-1", children: ready ? /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(TerminalMessage, { children: "Loading terminal\u2026" }), children: /* @__PURE__ */ jsx(
|
|
53
|
-
TerminalView,
|
|
54
|
-
{
|
|
55
|
-
apiUrl,
|
|
56
|
-
token: connection.token,
|
|
57
|
-
connectionId,
|
|
58
|
-
title,
|
|
59
|
-
subtitle,
|
|
60
|
-
isActive
|
|
61
|
-
}
|
|
62
|
-
) }) : /* @__PURE__ */ jsx(TerminalMessage, { children: connection.error ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
63
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: connection.error }),
|
|
64
|
-
onRetry && /* @__PURE__ */ jsx(
|
|
65
|
-
"button",
|
|
66
|
-
{
|
|
67
|
-
type: "button",
|
|
68
|
-
onClick: onRetry,
|
|
69
|
-
className: "mt-3 inline-flex items-center justify-center rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-muted",
|
|
70
|
-
children: "Reconnect"
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
] }) : connection.loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: connection.status === "provisioning" ? "Provisioning sandbox\u2026" : "Connecting\u2026" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
74
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Terminal not connected." }),
|
|
75
|
-
onRetry && /* @__PURE__ */ jsx(
|
|
76
|
-
"button",
|
|
77
|
-
{
|
|
78
|
-
type: "button",
|
|
79
|
-
onClick: onRetry,
|
|
80
|
-
className: "mt-3 inline-flex items-center justify-center rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-foreground transition hover:bg-muted",
|
|
81
|
-
children: "Connect"
|
|
82
|
-
}
|
|
83
|
-
)
|
|
84
|
-
] }) }) })
|
|
85
|
-
] });
|
|
86
|
-
}
|
|
87
|
-
function TerminalMessage({ children }) {
|
|
88
|
-
return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center p-6 text-center", children });
|
|
89
|
-
}
|
|
90
5
|
export {
|
|
91
|
-
WorkspaceTerminalPanel,
|
|
92
6
|
tabTerminalConnectionId,
|
|
93
7
|
useSandboxTerminalConnection
|
|
94
8
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-app",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.59",
|
|
4
4
|
"packageManager": "pnpm@11.17.0",
|
|
5
5
|
"description": "Build agent applications with typed chat, tools, sandboxes, integrations, billing, and evaluation.",
|
|
6
6
|
"keywords": [
|
|
@@ -393,6 +393,11 @@
|
|
|
393
393
|
"import": "./dist/vault/lazy.js",
|
|
394
394
|
"default": "./dist/vault/lazy.js"
|
|
395
395
|
},
|
|
396
|
+
"./vault/server": {
|
|
397
|
+
"types": "./dist/vault/server.d.ts",
|
|
398
|
+
"import": "./dist/vault/server.js",
|
|
399
|
+
"default": "./dist/vault/server.js"
|
|
400
|
+
},
|
|
396
401
|
"./brand": {
|
|
397
402
|
"types": "./dist/brand/index.d.ts",
|
|
398
403
|
"import": "./dist/brand/index.js",
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/** Define the connection details and status for a sandbox terminal session */
|
|
2
|
-
interface SandboxTerminalConnection {
|
|
3
|
-
runtimeUrl: string | null;
|
|
4
|
-
sidecarUrl: string | null;
|
|
5
|
-
token: string | null;
|
|
6
|
-
expiresAt: string | null;
|
|
7
|
-
status: string;
|
|
8
|
-
error: string | null;
|
|
9
|
-
loading: boolean;
|
|
10
|
-
sandboxId?: string;
|
|
11
|
-
}
|
|
12
|
-
/** Define the response structure for a sandbox terminal connection including URLs, token, status, and errors */
|
|
13
|
-
interface SandboxTerminalConnectionResponse {
|
|
14
|
-
runtimeUrl?: string;
|
|
15
|
-
sidecarUrl?: string;
|
|
16
|
-
token?: string;
|
|
17
|
-
expiresAt?: string;
|
|
18
|
-
status?: string;
|
|
19
|
-
error?: string;
|
|
20
|
-
sandboxId?: string;
|
|
21
|
-
}
|
|
22
|
-
/** Define options for configuring a sandbox terminal connection including workspace ID and connection parameters */
|
|
23
|
-
interface UseSandboxTerminalConnectionOptions {
|
|
24
|
-
workspaceId: string;
|
|
25
|
-
connectionUrl?: string | ((workspaceId: string) => string);
|
|
26
|
-
fetcher?: typeof fetch;
|
|
27
|
-
provisionPollIntervalMs?: number;
|
|
28
|
-
provisionPollTimeoutMs?: number;
|
|
29
|
-
tokenRefreshSkewMs?: number;
|
|
30
|
-
}
|
|
31
|
-
/** Resolve sandbox terminal connection status and provide a method to initiate the connection */
|
|
32
|
-
interface UseSandboxTerminalConnectionResult extends SandboxTerminalConnection {
|
|
33
|
-
connect: () => Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
/** Manage and maintain a sandbox terminal connection with automatic polling and token refresh handling */
|
|
36
|
-
declare function useSandboxTerminalConnection(opts: UseSandboxTerminalConnectionOptions): UseSandboxTerminalConnectionResult;
|
|
37
|
-
/**
|
|
38
|
-
* Stable-per-tab, unique-per-client terminal connection id.
|
|
39
|
-
*
|
|
40
|
-
* Persists in `sessionStorage` so a reload in the same tab reuses the id (the
|
|
41
|
-
* sidecar restores the same PTY session via `TerminalView.connectionId`), while
|
|
42
|
-
* separate tabs/windows each get a distinct id. Pass the result as
|
|
43
|
-
* `TerminalView`'s `connectionId`. Without it (e.g. gtm-agent today) every tab
|
|
44
|
-
* shares one connection id and their reconnects evict each other.
|
|
45
|
-
*
|
|
46
|
-
* Falls back to an ephemeral id when `sessionStorage` is unavailable (SSR,
|
|
47
|
-
* privacy mode) — still unique per call, just not reload-stable.
|
|
48
|
-
*/
|
|
49
|
-
declare function tabTerminalConnectionId(storageKey?: string): string;
|
|
50
|
-
|
|
51
|
-
export { type SandboxTerminalConnection as S, type UseSandboxTerminalConnectionOptions as U, type SandboxTerminalConnectionResponse as a, type UseSandboxTerminalConnectionResult as b, tabTerminalConnectionId as t, useSandboxTerminalConnection as u };
|
|
File without changes
|