@intentic/sandbox-contract 1.331.0 → 1.332.0
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/README.md +15 -0
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +11 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +37 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +14 -3
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +29 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.d.ts +2 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +7 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.d.ts +2 -0
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/logs.contract.d.ts +10 -0
- package/dist/contracts/logs.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +7 -0
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/offload.contract.d.ts +70 -0
- package/dist/contracts/offload.contract.d.ts.map +1 -1
- package/dist/contracts/offload.contract.js +10 -1
- package/dist/contracts/offload.contract.js.map +1 -1
- package/dist/contracts/privacy.contract.d.ts +14 -0
- package/dist/contracts/privacy.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.js +11 -1
- package/dist/contracts/privacy.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts +3 -0
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +103 -2
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +1 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +72 -94
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +19 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +17 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +4 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +46 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +6 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-words.d.ts.map +1 -1
- package/dist/events/agent-words.js +1 -1
- package/dist/events/agent-words.js.map +1 -1
- package/dist/events/resume.d.ts +3 -0
- package/dist/events/resume.d.ts.map +1 -1
- package/dist/events/resume.js +17 -2
- package/dist/events/resume.js.map +1 -1
- package/dist/events/sandbox-notice.d.ts +18 -0
- package/dist/events/sandbox-notice.d.ts.map +1 -1
- package/dist/events/sandbox-notice.js +27 -1
- package/dist/events/sandbox-notice.js.map +1 -1
- package/dist/events/system-events.d.ts +2 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +64 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +17 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/index.d.ts +313 -124
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/models/agent-catalog.d.ts.map +1 -1
- package/dist/models/agent-catalog.js +8 -7
- package/dist/models/agent-catalog.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts +3 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +10 -0
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/model-order.d.ts +1 -0
- package/dist/models/model-order.d.ts.map +1 -1
- package/dist/models/model-order.js +9 -0
- package/dist/models/model-order.js.map +1 -1
- package/dist/models/model-pins.d.ts.map +1 -1
- package/dist/models/model-pins.js.map +1 -1
- package/dist/models/model-roles.d.ts +25 -18
- package/dist/models/model-roles.d.ts.map +1 -1
- package/dist/models/model-roles.js +23 -27
- package/dist/models/model-roles.js.map +1 -1
- package/dist/models/model-setups.d.ts +46 -0
- package/dist/models/model-setups.d.ts.map +1 -0
- package/dist/models/model-setups.js +86 -0
- package/dist/models/model-setups.js.map +1 -0
- package/dist/models/plan-pools.d.ts +3 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +18 -6
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/models/provider-specs.d.ts +20 -0
- package/dist/models/provider-specs.d.ts.map +1 -1
- package/dist/models/provider-specs.js +13 -1
- package/dist/models/provider-specs.js.map +1 -1
- package/dist/policy/command-classes.d.ts.map +1 -1
- package/dist/policy/command-classes.js +26 -3
- package/dist/policy/command-classes.js.map +1 -1
- package/dist/policy/credential-material.d.ts +6 -0
- package/dist/policy/credential-material.d.ts.map +1 -1
- package/dist/policy/credential-material.js +37 -15
- package/dist/policy/credential-material.js.map +1 -1
- package/dist/policy/search-globs.d.ts +3 -0
- package/dist/policy/search-globs.d.ts.map +1 -1
- package/dist/policy/search-globs.js +118 -0
- package/dist/policy/search-globs.js.map +1 -1
- package/dist/policy/secret-hosts.d.ts +1 -0
- package/dist/policy/secret-hosts.d.ts.map +1 -1
- package/dist/policy/secret-hosts.js +1 -0
- package/dist/policy/secret-hosts.js.map +1 -1
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +1 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/doors/runner-protocol.d.ts +9 -0
- package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
- package/dist/protocol/doors/runner-protocol.js +5 -0
- package/dist/protocol/doors/runner-protocol.js.map +1 -1
- package/dist/protocol/pairing-redeem.d.ts +23 -0
- package/dist/protocol/pairing-redeem.d.ts.map +1 -0
- package/dist/protocol/pairing-redeem.js +44 -0
- package/dist/protocol/pairing-redeem.js.map +1 -0
- package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
- package/dist/protocol/peer-mcp-server.js +1 -1
- package/dist/protocol/peer-mcp-server.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +16 -0
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.js +2 -0
- package/dist/protocol/raw/raw-json-routes.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +8 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +2 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/protocol/sse.d.ts +20 -1
- package/dist/protocol/sse.d.ts.map +1 -1
- package/dist/protocol/sse.js +112 -17
- package/dist/protocol/sse.js.map +1 -1
- package/dist/schemas/agent.d.ts +32 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +12 -1
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/automations.d.ts +6 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/automations.js +3 -1
- package/dist/schemas/automations.js.map +1 -1
- package/dist/schemas/devices.d.ts +17 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +2 -1
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/engines.d.ts +8 -1
- package/dist/schemas/engines.d.ts.map +1 -1
- package/dist/schemas/engines.js +1 -1
- package/dist/schemas/engines.js.map +1 -1
- package/dist/schemas/git/git-scope.d.ts +20 -0
- package/dist/schemas/git/git-scope.d.ts.map +1 -0
- package/dist/schemas/git/git-scope.js +37 -0
- package/dist/schemas/git/git-scope.js.map +1 -0
- package/dist/schemas/git/git.d.ts +1 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +9 -1
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +42 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +3 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +1 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -1
- package/dist/schemas/keep-warm.js +1 -1
- package/dist/schemas/keep-warm.js.map +1 -1
- package/dist/schemas/logs.d.ts +21 -0
- package/dist/schemas/logs.d.ts.map +1 -1
- package/dist/schemas/logs.js +19 -0
- package/dist/schemas/logs.js.map +1 -1
- package/dist/schemas/metrics.d.ts +12 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +26 -8
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/needs.d.ts +19 -0
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +17 -3
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/offload.d.ts +154 -0
- package/dist/schemas/offload.d.ts.map +1 -1
- package/dist/schemas/offload.js +34 -0
- package/dist/schemas/offload.js.map +1 -1
- package/dist/schemas/privacy.d.ts +19 -0
- package/dist/schemas/privacy.d.ts.map +1 -1
- package/dist/schemas/privacy.js +17 -2
- package/dist/schemas/privacy.js.map +1 -1
- package/dist/schemas/project-bring-back.d.ts +172 -0
- package/dist/schemas/project-bring-back.d.ts.map +1 -0
- package/dist/schemas/project-bring-back.js +39 -0
- package/dist/schemas/project-bring-back.js.map +1 -0
- package/dist/schemas/providers/handoff.d.ts +77 -0
- package/dist/schemas/providers/handoff.d.ts.map +1 -0
- package/dist/schemas/providers/handoff.js +85 -0
- package/dist/schemas/providers/handoff.js.map +1 -0
- package/dist/schemas/providers/plan-limits.d.ts +27 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +8 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +4 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/usage.d.ts +3 -1
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +7 -2
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/run-targets.d.ts.map +1 -1
- package/dist/schemas/run-targets.js +5 -2
- package/dist/schemas/run-targets.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +1 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +21 -0
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +67 -100
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +41 -24
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/short-codes.d.ts +31 -0
- package/dist/schemas/short-codes.d.ts.map +1 -0
- package/dist/schemas/short-codes.js +28 -0
- package/dist/schemas/short-codes.js.map +1 -0
- package/dist/schemas/system.d.ts +130 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +37 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/state/definition.d.ts +64 -40
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +1 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +3 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/versions.d.ts +6 -0
- package/dist/state/versions.d.ts.map +1 -1
- package/dist/state/versions.js +47 -0
- package/dist/state/versions.js.map +1 -1
- package/dist/text/embed.d.ts +2 -1
- package/dist/text/embed.d.ts.map +1 -1
- package/dist/text/embed.js +10 -5
- package/dist/text/embed.js.map +1 -1
- package/dist/text/title.d.ts.map +1 -1
- package/dist/text/title.js +4 -7
- package/dist/text/title.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +2 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +21 -6
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +28 -6
- package/src/chores/probes.ts +3 -1
- package/src/contracts/agent.contract.ts +24 -3
- package/src/contracts/offload.contract.ts +21 -1
- package/src/contracts/privacy.contract.ts +13 -0
- package/src/contracts/settings.contract.ts +23 -0
- package/src/events/agent-events.ts +13 -0
- package/src/events/agent-words.ts +2 -3
- package/src/events/resume.test.ts +14 -2
- package/src/events/resume.ts +32 -3
- package/src/events/sandbox-notice.ts +31 -1
- package/src/events/transcript.ts +27 -2
- package/src/index.ts +5 -0
- package/src/models/agent-catalog.test.ts +4 -3
- package/src/models/agent-catalog.ts +10 -7
- package/src/models/agent-runtimes.ts +30 -1
- package/src/models/capability-ledger.test.ts +4 -0
- package/src/models/model-order.ts +13 -0
- package/src/models/model-pins.ts +5 -4
- package/src/models/model-roles.test.ts +9 -31
- package/src/models/model-roles.ts +35 -50
- package/src/models/model-setups.test.ts +100 -0
- package/src/models/model-setups.ts +151 -0
- package/src/models/plan-pools.test.ts +58 -0
- package/src/models/plan-pools.ts +38 -7
- package/src/models/provider-specs.ts +20 -2
- package/src/policy/command-classes.ts +34 -3
- package/src/policy/credential-material.ts +58 -16
- package/src/policy/search-globs.ts +135 -0
- package/src/policy/secret-hosts.ts +7 -0
- package/src/policy/tests/command-classes.test.ts +35 -0
- package/src/policy/tests/credential-material.test.ts +20 -0
- package/src/policy/tests/search-globs.test.ts +46 -1
- package/src/policy/tests/secret-hosts.test.ts +17 -1
- package/src/policy/turned-away.ts +2 -0
- package/src/protocol/doors/runner-protocol.ts +14 -1
- package/src/protocol/pairing-redeem.test.ts +86 -0
- package/src/protocol/pairing-redeem.ts +79 -0
- package/src/protocol/peer-mcp-server.ts +1 -2
- package/src/protocol/raw/raw-json-routes.ts +3 -0
- package/src/protocol/raw/raw-routes.ts +5 -0
- package/src/protocol/sse.test.ts +50 -1
- package/src/protocol/sse.ts +172 -26
- package/src/schemas/agent.ts +23 -2
- package/src/schemas/automations.ts +6 -2
- package/src/schemas/daemon-health.test.ts +28 -0
- package/src/schemas/devices.ts +11 -2
- package/src/schemas/engines.ts +1 -1
- package/src/schemas/git/git-scope.test.ts +91 -0
- package/src/schemas/git/git-scope.ts +71 -0
- package/src/schemas/git/git.ts +15 -1
- package/src/schemas/hosts.ts +7 -0
- package/src/schemas/keep-warm.ts +2 -1
- package/src/schemas/logs.ts +25 -0
- package/src/schemas/metrics.test.ts +80 -0
- package/src/schemas/metrics.ts +31 -9
- package/src/schemas/needs.ts +29 -3
- package/src/schemas/offload.ts +76 -0
- package/src/schemas/privacy.ts +27 -3
- package/src/schemas/project-bring-back.test.ts +15 -0
- package/src/schemas/project-bring-back.ts +78 -0
- package/src/schemas/providers/handoff.test.ts +42 -0
- package/src/schemas/providers/handoff.ts +128 -0
- package/src/schemas/providers/plan-limits.ts +14 -1
- package/src/schemas/providers/usage.ts +13 -7
- package/src/schemas/run-targets.ts +5 -2
- package/src/schemas/settings-history.ts +28 -0
- package/src/schemas/settings.test.ts +74 -21
- package/src/schemas/settings.ts +74 -44
- package/src/schemas/short-codes.ts +48 -0
- package/src/schemas/system.ts +63 -1
- package/src/state/history-state.ts +4 -0
- package/src/state/runtime-state.test.ts +4 -0
- package/src/state/runtime-state.ts +4 -0
- package/src/state/versions.test.ts +30 -1
- package/src/state/versions.ts +68 -0
- package/src/text/embed.test.ts +14 -0
- package/src/text/embed.ts +19 -10
- package/src/text/title.ts +5 -9
- package/src/text/transcript-fold.test.ts +61 -0
- package/src/text/transcript-fold.ts +31 -6
package/src/text/embed.test.ts
CHANGED
|
@@ -47,6 +47,20 @@ test("a JSON fetch answers the body or throws the refusal", async () => {
|
|
|
47
47
|
jest.fn(async () => new Response(JSON.stringify({ error: "rate limited" }), { status: 429 })),
|
|
48
48
|
);
|
|
49
49
|
await expect(fetchEmbedJson("https://x/y")).rejects.toMatchObject({ message: "rate limited", status: 429 });
|
|
50
|
+
stubGlobal(
|
|
51
|
+
"fetch",
|
|
52
|
+
jest.fn(async () => new Response("<html>bad gateway</html>", { status: 502 })),
|
|
53
|
+
);
|
|
54
|
+
await expect(fetchEmbedJson("https://x/y")).rejects.toMatchObject({ message: "request failed (502)", status: 502 });
|
|
55
|
+
unstubAllGlobals();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("a door that cannot be reached says so in words a visitor can read, without the URL", async () => {
|
|
59
|
+
stubGlobal(
|
|
60
|
+
"fetch",
|
|
61
|
+
jest.fn(async () => { throw new TypeError("Failed to fetch"); }),
|
|
62
|
+
);
|
|
63
|
+
await expect(fetchEmbedJson("https://x/y")).rejects.toThrow("The server could not be reached: Failed to fetch");
|
|
50
64
|
unstubAllGlobals();
|
|
51
65
|
});
|
|
52
66
|
|
package/src/text/embed.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { escapeHtml } from "@intentic/base/escape";
|
|
2
|
+
import { FetchJsonError, fetchJson } from "@intentic/base/fetch-json";
|
|
3
|
+
|
|
1
4
|
// The zod-free half of an embed's wire and look, shared by the two embeds (webchat-widget, issue-sdk) and the daemon's
|
|
2
|
-
// doors behind them.
|
|
3
|
-
// zod (measured at 1.1 MB for two strings).
|
|
5
|
+
// doors behind them. Nothing imported but base's dependency-free subpaths: this bundles into someone else's page, and
|
|
6
|
+
// the contract's barrel would drag in zod (measured at 1.1 MB for two strings).
|
|
4
7
|
|
|
5
8
|
// Where an embed talks to: the daemon it came from, and the automation it is the public face of.
|
|
6
9
|
export interface EmbedEndpoint {
|
|
@@ -29,13 +32,19 @@ export const embedFailure = async (response: Response): Promise<EmbedError> => {
|
|
|
29
32
|
export const embedUrl = ({ base, automationId }: EmbedEndpoint, slug: string, path: string): string =>
|
|
30
33
|
`${base}/${slug}/${encodeURIComponent(automationId)}/${path}`;
|
|
31
34
|
|
|
32
|
-
// One GET against a public door, answered as JSON or refused with the server's own sentence.
|
|
35
|
+
// One GET against a public door, answered as JSON or refused with the server's own sentence. Base's transport gives it
|
|
36
|
+
// a deadline, so a door that stalls fails in 30 s instead of leaving the visitor's widget waiting on it for minutes,
|
|
37
|
+
// and names a timeout as one. The label is what a visitor reads when the server cannot be reached.
|
|
33
38
|
export const fetchEmbedJson = async <T>(url: string): Promise<T> => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
try {
|
|
40
|
+
// SAFETY: the caller names the shape its door answers; the embed bundles no schema library to check it with.
|
|
41
|
+
return (await fetchJson(url, { label: "The server" })) as T;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// A refusal's body arrives as the start of its text; embedFailure reads the sentence out of it as for any response.
|
|
44
|
+
throw error instanceof FetchJsonError && error.kind === "status" && error.status !== undefined
|
|
45
|
+
? await embedFailure(new Response(error.body ?? "", { status: error.status }))
|
|
46
|
+
: error;
|
|
37
47
|
}
|
|
38
|
-
return (await response.json()) as T;
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
// Find a nonce whose SHA-256 of `salt:nonce` has `difficulty` leading zero bits, signed to one caller's id so it can't
|
|
@@ -81,6 +90,7 @@ export const solveProofOfWork = async (challenge: PowChallenge, insecure: string
|
|
|
81
90
|
if (nonce % BATCH === BATCH - 1) {
|
|
82
91
|
onProgress?.(nonce + 1);
|
|
83
92
|
// Hands the main thread back so the page (and the "checking..." line) keeps painting.
|
|
93
|
+
// allow(primitive-tiers): a yield to the page's event loop, not a wait; this bundle imports no async helpers.
|
|
84
94
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
85
95
|
}
|
|
86
96
|
}
|
|
@@ -174,6 +184,5 @@ export const onAccent = (accent: string, dark: string): string => {
|
|
|
174
184
|
};
|
|
175
185
|
|
|
176
186
|
// Text for innerHTML, safe both between tags and inside a quoted attribute: neither an owner's config nor what a
|
|
177
|
-
// visitor typed is trusted with markup.
|
|
178
|
-
export
|
|
179
|
-
value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char] ?? char);
|
|
187
|
+
// visitor typed is trusted with markup. Base's one escaper, re-exported for the embeds that import it from here.
|
|
188
|
+
export { escapeHtml };
|
package/src/text/title.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { oneLine } from "@intentic/base/one-line";
|
|
2
|
+
|
|
1
3
|
// The title a chat tab, fleet card and agent detail header show until a model names the conversation. Reads the prompt
|
|
2
4
|
// as prose first, stripping openers, quotes and fences, and cuts on a sentence or word boundary rather than truncating
|
|
3
5
|
// at a fixed length. Browser and daemon both call this so a prompt titles the same regardless of where it enters.
|
|
@@ -38,12 +40,6 @@ const IMPERATIVE =
|
|
|
38
40
|
// An unmistakable ask: an instruction verb, or a question mark.
|
|
39
41
|
const isAsk = (sentence: string): boolean => sentence.endsWith("?") || IMPERATIVE.test(sentence);
|
|
40
42
|
|
|
41
|
-
const collapse = (text: string): string =>
|
|
42
|
-
text
|
|
43
|
-
.replaceAll(/[\p{Cc}\p{Cf}]+/gu, " ")
|
|
44
|
-
.replaceAll(/\s+/g, " ")
|
|
45
|
-
.trim();
|
|
46
|
-
|
|
47
43
|
// Prompt lines minus fenced code blocks and quoted lines; those are evidence attached to the ask, not the ask itself.
|
|
48
44
|
const proseLines = (prompt: string): string[] => {
|
|
49
45
|
const lines: string[] = [];
|
|
@@ -180,7 +176,7 @@ export const planParts = (text: string): { title?: string; body: string } => {
|
|
|
180
176
|
// as its own first line.
|
|
181
177
|
export const deriveTitle = (prompt: string): string => {
|
|
182
178
|
const prose = proseLines(prompt)
|
|
183
|
-
.map(
|
|
179
|
+
.map((line) => oneLine(line))
|
|
184
180
|
.filter((line) => line !== "");
|
|
185
181
|
// Prose lines, openers stripped, letters required; first is the default title, rest matter only if narration.
|
|
186
182
|
const lines = prose.map(withoutOpener).filter((line) => /\p{L}/u.test(line));
|
|
@@ -197,6 +193,6 @@ export const deriveTitle = (prompt: string): string => {
|
|
|
197
193
|
return capitalized(clamped(firstSentence(collapseReferences(ask ?? primary))));
|
|
198
194
|
}
|
|
199
195
|
// Nothing had letters (code, quote or greeting only); title as the first raw line rather than leaving it blank.
|
|
200
|
-
const fallback = prose[0] ??
|
|
201
|
-
return clamped(collapseReferences(fallback === "" ?
|
|
196
|
+
const fallback = prose[0] ?? oneLine(prompt.replaceAll(/^\s*(?:```|~~~).*$/gm, ""));
|
|
197
|
+
return clamped(collapseReferences(fallback === "" ? oneLine(prompt) : fallback));
|
|
202
198
|
};
|
|
@@ -1073,3 +1073,64 @@ describe(`the code beside a notice's words`, () => {
|
|
|
1073
1073
|
expect(sandboxNoticeOf({ noticeCode: { code: `landConflict`, params: { files: `three` } } })).toBeUndefined();
|
|
1074
1074
|
});
|
|
1075
1075
|
});
|
|
1076
|
+
|
|
1077
|
+
// A runtime's frames carry no clock; the live fold stamps what it folds, so a stored record can say when each call ran
|
|
1078
|
+
// and how long it took. A fold without a clock (a replay, these tests above) stamps nothing.
|
|
1079
|
+
describe("TranscriptFold with a clock", () => {
|
|
1080
|
+
const ticking = (): (() => number) => {
|
|
1081
|
+
let now = SENT_AT;
|
|
1082
|
+
return () => (now += 1000);
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
it("stamps each card's start and the arrival of its result, and each assistant and notice row's opening", () => {
|
|
1086
|
+
const fold = new TranscriptFold(openingOf("run"), ticking());
|
|
1087
|
+
for (const event of [
|
|
1088
|
+
{ kind: "delta", text: "running" },
|
|
1089
|
+
{ kind: "tool_call", id: "t1", name: "Bash", category: "execute", status: "in_progress", target: "pnpm test" },
|
|
1090
|
+
{ kind: "tool_call_update", id: "t1", content: [{ type: "text", text: "partial" }] },
|
|
1091
|
+
{ kind: "tool_call_update", id: "t1", status: "failed", content: [{ type: "text", text: "1 failed" }], exitCode: 1 },
|
|
1092
|
+
// Re-sending a final status does not move when the result arrived.
|
|
1093
|
+
{ kind: "tool_call_update", id: "t1", status: "failed" },
|
|
1094
|
+
{ kind: "compact", trigger: "auto" },
|
|
1095
|
+
] satisfies AgentEvent[]) {
|
|
1096
|
+
fold.apply(event);
|
|
1097
|
+
}
|
|
1098
|
+
const [user, assistant, notice] = fold.rows;
|
|
1099
|
+
expect(user).toEqual(userRow("run", SENT_AT, []));
|
|
1100
|
+
expect(assistant?.openedAt).toBe(SENT_AT + 1000);
|
|
1101
|
+
expect(assistant?.tools).toEqual([
|
|
1102
|
+
{
|
|
1103
|
+
id: "t1",
|
|
1104
|
+
name: "Bash",
|
|
1105
|
+
category: "execute",
|
|
1106
|
+
status: "failed",
|
|
1107
|
+
target: "pnpm test",
|
|
1108
|
+
content: [{ type: "text", text: "1 failed" }],
|
|
1109
|
+
startedAt: SENT_AT + 2000,
|
|
1110
|
+
endedAt: SENT_AT + 3000,
|
|
1111
|
+
exitCode: 1,
|
|
1112
|
+
},
|
|
1113
|
+
]);
|
|
1114
|
+
expect(notice).toMatchObject({ role: "notice", openedAt: SENT_AT + 4000 });
|
|
1115
|
+
// Every stamp survives the schema a stored record is read back through.
|
|
1116
|
+
expect(fold.rows.map((row) => TranscriptRowSchema.parse(row))).toEqual(fold.rows);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it("dates a call that arrives already finished to the moment it was seen, and a nested call like any other", () => {
|
|
1120
|
+
const fold = new TranscriptFold(openingOf("look"), ticking());
|
|
1121
|
+
fold.apply({ kind: "tool_call", id: "a1", name: "Agent", category: "other", status: "in_progress", input: '{"prompt":"look"}' });
|
|
1122
|
+
const [patch] = fold.apply({ kind: "tool_call", id: "r1", name: "Read", category: "read", status: "completed", parentToolUseId: "a1" });
|
|
1123
|
+
expect(patch).toMatchObject({ op: "tool", tool: { id: "r1", startedAt: SENT_AT + 3000, endedAt: SENT_AT + 3000 }, parent: "a1" });
|
|
1124
|
+
expect(fold.rows[1]?.tools?.[0]).toMatchObject({ input: '{"prompt":"look"}', startedAt: SENT_AT + 2000 });
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it("keeps a daemon-written row's own moment, and never stamps a user's row", () => {
|
|
1128
|
+
const fold = new TranscriptFold(openingOf("go"), ticking());
|
|
1129
|
+
fold.note({ role: "notice", text: "kept", openedAt: 7 });
|
|
1130
|
+
fold.apply({ kind: "steer", text: "and this", sentAt: SENT_AT + 5 });
|
|
1131
|
+
expect(fold.rows.slice(1)).toEqual([
|
|
1132
|
+
{ role: "notice", text: "kept", openedAt: 7 },
|
|
1133
|
+
{ role: "user", text: "and this", sentAt: SENT_AT + 5 },
|
|
1134
|
+
]);
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "../events/transcript.js";
|
|
14
14
|
import { contextTrimLine, contextTrimNotice } from "../schemas/context-trim.js";
|
|
15
15
|
import { keptWarmLine, keptWarmNotice } from "../schemas/keep-warm.js";
|
|
16
|
+
import { handedOffLine, handedOffNotice } from "../schemas/providers/handoff.js";
|
|
16
17
|
import { isLandConflict } from "../events/land-conflict.js";
|
|
17
18
|
import { turnedAwayCode } from "../policy/turned-away.js";
|
|
18
19
|
import { mentionedPathTokens } from "./mentions.js";
|
|
@@ -56,7 +57,12 @@ const resultWords = (tool: TranscriptTool): string =>
|
|
|
56
57
|
.map((entry) => entry.text)
|
|
57
58
|
.join("\n");
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
// Whether a call's status is final: the moment it turns so is when its result arrived.
|
|
61
|
+
const ended = (status: TranscriptTool["status"]): boolean => status === "completed" || status === "failed";
|
|
62
|
+
|
|
63
|
+
// `at` is the fold's clock reading as the call is seen, absent for a fold without one; a call that arrives already
|
|
64
|
+
// finished started and ended in that same moment, as far as anyone here can tell.
|
|
65
|
+
const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>, at: number | undefined): TranscriptTool => ({
|
|
60
66
|
id: event.id,
|
|
61
67
|
name: event.name,
|
|
62
68
|
category: event.category,
|
|
@@ -64,6 +70,8 @@ const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>): TranscriptTo
|
|
|
64
70
|
...(event.target !== undefined ? { target: event.target } : {}),
|
|
65
71
|
...(event.locations !== undefined ? { locations: event.locations } : {}),
|
|
66
72
|
...(event.content !== undefined ? { content: event.content } : {}),
|
|
73
|
+
...(event.input !== undefined ? { input: event.input } : {}),
|
|
74
|
+
...(at !== undefined ? { startedAt: at, ...(ended(event.status) ? { endedAt: at } : {}) } : {}),
|
|
67
75
|
});
|
|
68
76
|
|
|
69
77
|
// Whether a just-retired prose bubble says exactly what the plan about to be drawn says, so the request can take its
|
|
@@ -339,7 +347,13 @@ export class TranscriptFold {
|
|
|
339
347
|
// Set by `retract`: this turn was refused before it ran, and its message went back to the conversation's queue.
|
|
340
348
|
private unrun = false;
|
|
341
349
|
|
|
342
|
-
|
|
350
|
+
// `now`, when given, stamps each card's start and end and each assistant or notice row's opening as the frame that
|
|
351
|
+
// brings it is folded: a runtime's frames carry no clock, so the live fold is the one place these moments are known.
|
|
352
|
+
// A fold replaying frames after the fact is given none, since its own clock would date them all to the replay.
|
|
353
|
+
constructor(
|
|
354
|
+
opening: readonly TranscriptRow[],
|
|
355
|
+
private readonly now?: () => number,
|
|
356
|
+
) {
|
|
343
357
|
for (const [index, row] of opening.entries()) {
|
|
344
358
|
this.rows.push(row);
|
|
345
359
|
// A run carried on after a restart opens on the cards it had raised, which it raises again: each is found
|
|
@@ -387,7 +401,7 @@ export class TranscriptFold {
|
|
|
387
401
|
return [];
|
|
388
402
|
case "tool_call": {
|
|
389
403
|
const [index, opened] = this.open();
|
|
390
|
-
const tool = cardOf(event);
|
|
404
|
+
const tool = cardOf(event, this.now?.());
|
|
391
405
|
const row = this.rows[index]!;
|
|
392
406
|
row.tools = [...(row.tools ?? []), tool];
|
|
393
407
|
this.cards.set(tool.id, { tool, row: index });
|
|
@@ -399,6 +413,13 @@ export class TranscriptFold {
|
|
|
399
413
|
return this.patchCard(event.id, (tool) => {
|
|
400
414
|
if (event.status !== undefined) {
|
|
401
415
|
tool.status = event.status;
|
|
416
|
+
// The first final status is when the result arrived; a later frame re-sending it is not.
|
|
417
|
+
if (ended(event.status) && tool.endedAt === undefined && this.now !== undefined) {
|
|
418
|
+
tool.endedAt = this.now();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (event.exitCode !== undefined) {
|
|
422
|
+
tool.exitCode = event.exitCode;
|
|
402
423
|
}
|
|
403
424
|
if (event.content !== undefined) {
|
|
404
425
|
tool.content = event.content;
|
|
@@ -460,6 +481,8 @@ export class TranscriptFold {
|
|
|
460
481
|
// A row of its own rather than a stamp on the message: what was left out is not part of what was sent,
|
|
461
482
|
// and the fold beside the message only ever lists notes that actually rode.
|
|
462
483
|
return this.pushRow({ role: "notice", text: contextTrimLine(event), noticeCode: noticeCode(contextTrimNotice(event)) });
|
|
484
|
+
case "handoff":
|
|
485
|
+
return this.pushRow({ role: "notice", text: handedOffLine(event), noticeCode: noticeCode(handedOffNotice(event)) });
|
|
463
486
|
case "worktree":
|
|
464
487
|
return event.sync === undefined ? [] : this.synced(event.sync);
|
|
465
488
|
case "landed":
|
|
@@ -735,7 +758,7 @@ export class TranscriptFold {
|
|
|
735
758
|
return [{ op: "toolThinking", index: place.row, id: parent, text: event.text }];
|
|
736
759
|
}
|
|
737
760
|
if (event.kind === "tool_call") {
|
|
738
|
-
const child = cardOf(event);
|
|
761
|
+
const child = cardOf(event, this.now?.());
|
|
739
762
|
place.tool.children = [...(place.tool.children ?? []), child];
|
|
740
763
|
this.cards.set(child.id, { tool: child, row: place.row, parent });
|
|
741
764
|
this.claimById(child);
|
|
@@ -820,7 +843,7 @@ export class TranscriptFold {
|
|
|
820
843
|
if (this.bubble !== undefined) {
|
|
821
844
|
return [this.bubble, []];
|
|
822
845
|
}
|
|
823
|
-
const row: TranscriptRow = { role: "assistant", text: "" };
|
|
846
|
+
const row: TranscriptRow = { role: "assistant", text: "", ...(this.now === undefined ? {} : { openedAt: this.now() }) };
|
|
824
847
|
this.rows.push(row);
|
|
825
848
|
this.bubble = this.rows.length - 1;
|
|
826
849
|
return [this.bubble, [{ op: "append", row: structuredClone(row) }]];
|
|
@@ -842,8 +865,10 @@ export class TranscriptFold {
|
|
|
842
865
|
}
|
|
843
866
|
|
|
844
867
|
// Pushes a row that is not the open bubble; closes the bubble first so order stays: what came before, then this.
|
|
845
|
-
|
|
868
|
+
// A row other than a user's is stamped with when it landed, unless it came with its own moment.
|
|
869
|
+
private pushRow(given: TranscriptRow): TranscriptPatch[] {
|
|
846
870
|
const closed = this.closeBubble();
|
|
871
|
+
const row = given.role === "user" || given.openedAt !== undefined || this.now === undefined ? given : { ...given, openedAt: this.now() };
|
|
847
872
|
this.rows.push(row);
|
|
848
873
|
return [...closed, { op: "append", row: structuredClone(row) }];
|
|
849
874
|
}
|