@intentic/sandbox-contract 1.309.0 → 1.311.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 +38 -159
- package/dist/contracts/accounts.contract.d.ts +3 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +15 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +596 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +13 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +2 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +1 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +45 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +11 -15
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +59 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +20 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +6 -1
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/system-events.d.ts +48 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +5 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +9 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/front/generated/wire.d.ts +74 -0
- package/dist/front/generated/wire.d.ts.map +1 -0
- package/dist/front/generated/wire.js +2 -0
- package/dist/front/generated/wire.js.map +1 -0
- package/dist/index.d.ts +717 -74
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/policy/command-run.d.ts +1 -1
- package/dist/policy/command-run.d.ts.map +1 -1
- package/dist/policy/command-run.js +7 -4
- package/dist/policy/command-run.js.map +1 -1
- package/dist/protocol/ingress-protocol.d.ts.map +1 -1
- package/dist/protocol/ingress-protocol.js +15 -0
- package/dist/protocol/ingress-protocol.js.map +1 -1
- package/dist/protocol/upgrade-relay.d.ts +4 -0
- package/dist/protocol/upgrade-relay.d.ts.map +1 -0
- package/dist/protocol/upgrade-relay.js +25 -0
- package/dist/protocol/upgrade-relay.js.map +1 -0
- package/dist/schemas/agents.d.ts +72 -3
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +10 -7
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +24 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +3 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +4 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/devices.d.ts +32 -6
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +8 -6
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +68 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -0
- package/dist/schemas/keep-warm.js +74 -0
- package/dist/schemas/keep-warm.js.map +1 -0
- package/dist/schemas/metrics.d.ts +24 -25
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +6 -31
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +3 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +4 -0
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +6 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +4 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings.d.ts +13 -29
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +48 -11
- package/dist/schemas/settings.js.map +1 -1
- package/dist/state/definition.d.ts +24 -20
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +1 -0
- package/dist/state/fix-stance.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/workspace-state.d.ts +0 -7
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +0 -8
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +2 -0
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +20 -7
- package/dist/text/transcript-fold.js.map +1 -1
- package/dist/time/zone.d.ts +1 -0
- package/dist/time/zone.d.ts.map +1 -1
- package/dist/time/zone.js +5 -0
- package/dist/time/zone.js.map +1 -1
- package/package.json +27 -5
- package/src/chores/chores.test.ts +0 -1
- package/src/chores/digest.test.ts +0 -1
- package/src/chores/probes.test.ts +0 -1
- package/src/chores/stack.test.ts +0 -1
- package/src/chores/verdict.test.ts +0 -1
- package/src/contracts/agents.contract.ts +14 -2
- package/src/events/agent-events.ts +9 -1
- package/src/events/agent-words.test.ts +0 -1
- package/src/events/resume.test.ts +0 -1
- package/src/events/transcript.ts +14 -3
- package/src/events/watch-wake.test.ts +0 -1
- package/src/front/generated/wire.ts +73 -0
- package/src/ids/conversation-ids.test.ts +1 -2
- package/src/ids/hostnames.fixture.json +20 -0
- package/src/ids/hostnames.test.ts +20 -1
- package/src/ids/share-paths.test.ts +0 -1
- package/src/ids/tunnel-ids.test.ts +0 -1
- package/src/index.ts +1 -3
- package/src/models/agent-catalog.test.ts +0 -1
- package/src/models/capability-ledger.test.ts +0 -1
- package/src/models/model-label.test.ts +0 -1
- package/src/models/model-offer.test.ts +0 -1
- package/src/models/model-order.test.ts +0 -1
- package/src/models/model-pins.test.ts +0 -1
- package/src/models/model-roles.test.ts +0 -1
- package/src/models/plan-pools.test.ts +0 -1
- package/src/models/provider-specs.test.ts +0 -1
- package/src/policy/batch-runs.test.ts +0 -1
- package/src/policy/command-classes.test.ts +0 -1
- package/src/policy/command-run.test.ts +35 -0
- package/src/policy/command-run.ts +10 -5
- package/src/policy/credential-material.test.ts +0 -1
- package/src/policy/fence-paths.test.ts +0 -1
- package/src/policy/overlay-lint.test.ts +0 -1
- package/src/policy/owner-ticket.test.ts +0 -1
- package/src/policy/persona-home.test.ts +0 -1
- package/src/policy/reserved-servers.test.ts +0 -1
- package/src/policy/safety-policy.test.ts +0 -1
- package/src/policy/search-globs.test.ts +0 -1
- package/src/protocol/container-requirements.test.ts +0 -1
- package/src/protocol/ingress-contract.test.ts +0 -1
- package/src/protocol/ingress-protocol.test.ts +45 -2
- package/src/protocol/ingress-protocol.ts +25 -4
- package/src/protocol/peer-dial.test.ts +1 -2
- package/src/protocol/peer-mcp-server.test.ts +0 -1
- package/src/protocol/routes.test.ts +6 -3
- package/src/protocol/tunnel-heartbeat.test.ts +2 -3
- package/src/protocol/upgrade-relay.ts +28 -0
- package/src/schemas/agent.test.ts +0 -1
- package/src/schemas/agents.ts +14 -7
- package/src/schemas/areas.test.ts +1 -2
- package/src/schemas/capabilities.ts +6 -0
- package/src/schemas/ci.ts +3 -9
- package/src/schemas/context-trim.test.ts +0 -1
- package/src/schemas/dev-rebuild-log.test.ts +0 -1
- package/src/schemas/devices.ts +16 -9
- package/src/schemas/git/git.test.ts +0 -1
- package/src/schemas/keep-warm.test.ts +68 -0
- package/src/schemas/keep-warm.ts +124 -0
- package/src/schemas/metrics.ts +33 -64
- package/src/schemas/providers/provider-oauth.ts +6 -0
- package/src/schemas/providers/usage.ts +7 -0
- package/src/schemas/settings.test.ts +41 -7
- package/src/schemas/settings.ts +78 -44
- package/src/schemas/version-seam.test.ts +0 -1
- package/src/state/contract-lock.test.ts +0 -1
- package/src/state/fix-attempt-plan.test.ts +0 -1
- package/src/state/fix-stance.test.ts +14 -1
- package/src/state/fix-stance.ts +2 -0
- package/src/state/landed-commit.test.ts +0 -1
- package/src/state/runtime-state.test.ts +0 -1
- package/src/state/runtime-state.ts +4 -0
- package/src/state/versions.test.ts +0 -1
- package/src/state/workspace-state.test.ts +0 -3
- package/src/state/workspace-state.ts +0 -14
- package/src/text/archives.test.ts +0 -1
- package/src/text/documents.test.ts +0 -1
- package/src/text/embed.test.ts +2 -3
- package/src/text/emoji.test.ts +0 -1
- package/src/text/mentions.test.ts +0 -1
- package/src/text/path-refs.test.ts +0 -1
- package/src/text/title.test.ts +0 -1
- package/src/text/transcript-fold.test.ts +32 -4
- package/src/text/transcript-fold.ts +39 -9
- package/src/text/whisper.test.ts +0 -1
- package/src/text/workflow-faults.test.ts +0 -1
- package/src/time/zone.test.ts +0 -1
- package/src/time/zone.ts +7 -0
- package/src/webext/index.ts +1 -1
- package/dist/contracts/prepush.contract.d.ts +0 -32
- package/dist/contracts/prepush.contract.d.ts.map +0 -1
- package/dist/contracts/prepush.contract.js +0 -41
- package/dist/contracts/prepush.contract.js.map +0 -1
- package/src/contracts/prepush.contract.ts +0 -58
package/src/schemas/settings.ts
CHANGED
|
@@ -13,39 +13,28 @@ export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
|
|
|
13
13
|
export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
|
|
14
14
|
// Excludes "custom": there is nothing to fetch, it's whatever the owner already typed into the settings field.
|
|
15
15
|
export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
|
|
16
|
-
// Rules: "at this moment, if this is true, do this"
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
// Five places the daemon already stops to decide something; this names those decisions rather than inventing new ones.
|
|
16
|
+
// Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version) and switch the
|
|
17
|
+
// daemon's built-in reviews; every command a moment runs is a repository's own check (`<repo>/.intentic/checks.json`),
|
|
18
|
+
// compiled into this same table by the daemon, so no command lives in settings.
|
|
21
19
|
export const RuleMomentSchema = z.enum([
|
|
22
20
|
// A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
|
|
23
21
|
"file.edited",
|
|
24
22
|
// The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
|
|
25
23
|
"turn.ending",
|
|
26
|
-
// Code is about to leave the machine. A rule here gates the push on its own exit code.
|
|
27
|
-
"push.starting",
|
|
28
24
|
// An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
|
|
29
25
|
"agent.finished",
|
|
30
26
|
// An agent's delta has just reached the main tree. A rule here decides what becomes of it there.
|
|
31
27
|
"agent.landed",
|
|
32
28
|
]);
|
|
33
29
|
export type RuleMoment = z.infer<typeof RuleMomentSchema>;
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// verdict: allows or holds what is about to happen (a pass with nothing to run, told which way to go).
|
|
38
|
-
// builtin: invokes a named daemon behaviour the table has no business expressing itself.
|
|
39
|
-
// Each reads a record only the daemon keeps, which is what makes it a built-in rather than a command.
|
|
40
|
-
// verify-edits: what a turn edited, against what it ran.
|
|
41
|
-
// verify-removals: what a turn deleted, against the repo's own history (a `git log` question, not a shell one-liner).
|
|
30
|
+
// command: a repository's check; its exit code is the verdict.
|
|
31
|
+
// verdict: allows or holds what is about to happen.
|
|
32
|
+
// builtin: a daemon behaviour that reads a record only the daemon keeps.
|
|
42
33
|
// verify-ui-edits: rendered surfaces a turn changed, against whether it ever looked at one.
|
|
43
|
-
// verify-tests: whether a touched test's assertions got weaker than at HEAD, or would have passed before its own
|
|
44
|
-
// change.
|
|
45
34
|
// version-landed: commits what a land brought, in the main tree, under the subject drafted for it, and the tree's own
|
|
46
35
|
// remainder before the next isolated turn starts; the only way a person who never commits keeps every agent seeing
|
|
47
36
|
// the latest tree, since worktrees are cut from HEAD.
|
|
48
|
-
export const RuleBuiltinSchema = z.enum(["verify-
|
|
37
|
+
export const RuleBuiltinSchema = z.enum(["verify-ui-edits", "version-landed"]);
|
|
49
38
|
export type RuleBuiltin = z.infer<typeof RuleBuiltinSchema>;
|
|
50
39
|
export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
51
40
|
z.object({
|
|
@@ -54,7 +43,6 @@ export const RuleActionSchema = z.discriminatedUnion("kind", [
|
|
|
54
43
|
// Past this, the process group is killed and the run is `failed`, never a silent pass.
|
|
55
44
|
timeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
|
|
56
45
|
}),
|
|
57
|
-
z.object({ kind: z.literal("instruct"), text: z.string().min(1).max(4000) }),
|
|
58
46
|
z.object({ kind: z.literal("verdict"), verdict: z.enum(["allow", "hold"]) }),
|
|
59
47
|
z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
|
|
60
48
|
]);
|
|
@@ -63,8 +51,7 @@ export type RuleAction = z.infer<typeof RuleActionSchema>;
|
|
|
63
51
|
// defaults to HELD unless a rule explicitly allows it.
|
|
64
52
|
export const RuleOutcomeSchema = z.enum(["clean", "error", "conflict", "checks-failed"]);
|
|
65
53
|
export type RuleOutcome = z.infer<typeof RuleOutcomeSchema>;
|
|
66
|
-
//
|
|
67
|
-
// the rule always matches.
|
|
54
|
+
// Every key absent means the rule always matches.
|
|
68
55
|
export const RuleConditionSchema = z.object({
|
|
69
56
|
// A workspace repo id, or "root". Absent ⇒ any.
|
|
70
57
|
repo: z.string().min(1).optional(),
|
|
@@ -80,15 +67,13 @@ export type RuleCondition = z.infer<typeof RuleConditionSchema>;
|
|
|
80
67
|
// validated here, not left to the consumer — the alternative is a rule that saves cleanly and silently does nothing.
|
|
81
68
|
const MOMENT_ACTIONS: Record<RuleMoment, readonly RuleAction["kind"][]> = {
|
|
82
69
|
"file.edited": ["command"],
|
|
83
|
-
"turn.ending": ["builtin", "
|
|
84
|
-
"push.starting": ["command"],
|
|
70
|
+
"turn.ending": ["builtin", "command"],
|
|
85
71
|
"agent.finished": ["verdict"],
|
|
86
72
|
"agent.landed": ["builtin"],
|
|
87
73
|
};
|
|
88
|
-
// A built-in reads a record only one moment keeps, so it stands at that moment alone
|
|
89
|
-
// ledgers, the versioner reads a landing's claim.
|
|
74
|
+
// A built-in reads a record only one moment keeps, so it stands at that moment alone.
|
|
90
75
|
const MOMENT_BUILTINS: Partial<Record<RuleMoment, readonly RuleBuiltin[]>> = {
|
|
91
|
-
"turn.ending": ["verify-
|
|
76
|
+
"turn.ending": ["verify-ui-edits"],
|
|
92
77
|
"agent.landed": ["version-landed"],
|
|
93
78
|
};
|
|
94
79
|
export const RuleSchema = z
|
|
@@ -402,6 +387,36 @@ export const SandboxSettingsSchema = z.object({
|
|
|
402
387
|
.describe(
|
|
403
388
|
"When a spent usage limit moves a turn to another account, carry the provider session (the model keeps everything, and re-reads all of it once on the other account) while the conversation's context is under this many tokens; at or above it, start a fresh session with the sandbox's measured brief instead. Zero always starts fresh.",
|
|
404
389
|
),
|
|
390
|
+
// Off by default: every refresh spends the account's own allowance on a conversation nobody is using yet.
|
|
391
|
+
keepWarm: z
|
|
392
|
+
.boolean()
|
|
393
|
+
.default(false)
|
|
394
|
+
.describe(
|
|
395
|
+
"Keep a Claude conversation's prompt cache warm after each turn a person asked for, so coming back to it hours later costs a cache read instead of re-sending everything. Each refresh re-reads the cached context at the cache price and adds nothing to the conversation. Any one conversation can be kept warm or let cool by hand either way.",
|
|
396
|
+
),
|
|
397
|
+
keepWarmHours: z
|
|
398
|
+
.number()
|
|
399
|
+
.min(1)
|
|
400
|
+
.max(8)
|
|
401
|
+
.default(4)
|
|
402
|
+
.describe(
|
|
403
|
+
"How long an idle conversation is kept warm after its last turn, in hours. Shortened where refreshing would cost more than the cold resume it saves, and at midnight where the agent runs, when the date in its prompt changes.",
|
|
404
|
+
),
|
|
405
|
+
keepWarmMinTokens: z
|
|
406
|
+
.number()
|
|
407
|
+
.int()
|
|
408
|
+
.min(0)
|
|
409
|
+
.default(100_000)
|
|
410
|
+
.describe("Only conversations at least this large, in tokens, are kept warm by themselves: a small one is cheap to re-read anyway."),
|
|
411
|
+
keepWarmReserve: z
|
|
412
|
+
.number()
|
|
413
|
+
.int()
|
|
414
|
+
.min(0)
|
|
415
|
+
.max(90)
|
|
416
|
+
.default(15)
|
|
417
|
+
.describe(
|
|
418
|
+
"How much of an account's usage limit, in percent, keeping conversations warm must leave untouched for real work. Refreshing stops once any limit that account's model spends is fuller than that.",
|
|
419
|
+
),
|
|
405
420
|
// Worth it since the container is recreated on every update or environment approval — otherwise approving a
|
|
406
421
|
// Dockerfile change costs the run that asked for it.
|
|
407
422
|
// What happens to breakage found after the work that caused it has left the turn: a land that turns the main tree's own
|
|
@@ -429,14 +444,17 @@ export const SandboxSettingsSchema = z.object({
|
|
|
429
444
|
.describe(
|
|
430
445
|
"Which repositories may run the checks they declare for themselves, and exactly which version of those checks you agreed to. A repository's declaration does nothing until it appears here, the same rule git keeps for hooks, which are never cloned; and a declaration that changes afterwards is held until you look at it again.",
|
|
431
446
|
),
|
|
432
|
-
//
|
|
433
|
-
//
|
|
447
|
+
// The owner's alone, since a rule can hold work. A command is refused here: it belongs to the repository it checks,
|
|
448
|
+
// in that repository's `.intentic/checks.json`, never in settings.
|
|
434
449
|
rules: z
|
|
435
450
|
.array(RuleSchema)
|
|
436
451
|
.max(50)
|
|
437
452
|
.default([])
|
|
453
|
+
.refine((rules) => rules.every((rule) => rule.action.kind !== "command"), {
|
|
454
|
+
message: "a command belongs in the repository's own .intentic/checks.json, not in settings",
|
|
455
|
+
})
|
|
438
456
|
.describe(
|
|
439
|
-
"Standing
|
|
457
|
+
"Standing decisions about the sandbox's own work: land or hold finished work, save a version of what landed, ask the assistant to look at an interface it changed. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment. A command to run is a repository's own check, declared in its .intentic/checks.json.",
|
|
440
458
|
),
|
|
441
459
|
automationFailureLimit: z
|
|
442
460
|
.number()
|
|
@@ -608,27 +626,23 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
|
608
626
|
// spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
|
|
609
627
|
export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
|
|
610
628
|
|
|
611
|
-
// Named for the occasion as a repository would say it
|
|
612
|
-
//
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
// that declares a whole-tree command here pays it per edit, so the command has to take the file.
|
|
618
|
-
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "push"]);
|
|
629
|
+
// Named for the occasion as a repository would say it: `edit` is `file.edited`, `turn` is `turn.ending`, and `land` is
|
|
630
|
+
// the daemon's run over the main tree after an install a land caused (rules/repo-checks.ts maps them). A push is gated by
|
|
631
|
+
// the repository's own git pre-push hook, which every push runs, the app's included.
|
|
632
|
+
// `edit` runs on one file (`{file}`) and its output rides back in the edit's own response, so a command declared there is
|
|
633
|
+
// paid per edit and has to take the file.
|
|
634
|
+
export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
|
|
619
635
|
export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
|
|
620
636
|
|
|
621
637
|
export const RepoCheckSchema = z.object({
|
|
622
638
|
when: RepoCheckMomentSchema.describe(
|
|
623
|
-
"When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `
|
|
639
|
+
"When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `land` on the main tree after finished work lands (absent, the package's own verify or test script runs there).",
|
|
624
640
|
),
|
|
625
641
|
run: z.string().min(1).max(500).describe("The command, run in this repository's own directory, so it reads as it would in a terminal there."),
|
|
626
642
|
label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
|
|
627
|
-
// Same ceiling as a rule's own command; past it the process group is killed and the run is a failure
|
|
628
|
-
// silent pass.
|
|
643
|
+
// Same ceiling as a rule's own command; past it the process group is killed and the run is a failure.
|
|
629
644
|
timeoutMs: z.number().min(60_000).max(3_600_000).optional().describe("How long it may take before it is killed and counted as failed."),
|
|
630
|
-
// Repo-relative
|
|
631
|
-
// since a rule's globs are workspace-relative.
|
|
645
|
+
// Repo-relative; the daemon prefixes the repo id before matching, since a rule's globs are workspace-relative.
|
|
632
646
|
paths: z
|
|
633
647
|
.array(z.string().min(1))
|
|
634
648
|
.max(20)
|
|
@@ -638,7 +652,16 @@ export const RepoCheckSchema = z.object({
|
|
|
638
652
|
export type RepoCheck = z.infer<typeof RepoCheckSchema>;
|
|
639
653
|
|
|
640
654
|
// The file itself. One key, so a second concern can be added later without breaking a file anyone has written.
|
|
641
|
-
export const RepoChecksFileSchema = z.object({
|
|
655
|
+
export const RepoChecksFileSchema = z.object({
|
|
656
|
+
checks: z
|
|
657
|
+
.array(RepoCheckSchema)
|
|
658
|
+
.max(10)
|
|
659
|
+
.default([])
|
|
660
|
+
// One land check per repository: it is THE verdict on the main tree the push hook replays, not one of several.
|
|
661
|
+
.refine((checks) => checks.filter((check) => check.when === "land").length <= 1, { message: "a repository declares at most one land check" })
|
|
662
|
+
// A land checks the whole tree an install settled, not a change, so there is nothing for a glob to narrow.
|
|
663
|
+
.refine((checks) => checks.every((check) => check.when !== "land" || check.paths === undefined), { message: "a land check takes no paths" }),
|
|
664
|
+
});
|
|
642
665
|
export type RepoChecksFile = z.infer<typeof RepoChecksFileSchema>;
|
|
643
666
|
|
|
644
667
|
// One repository, as a screen reads it: what it declares, and where that stands with the owner.
|
|
@@ -646,6 +669,11 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
646
669
|
repo: z.string().describe('Which repository, by its workspace id ("root" is the workspace itself).'),
|
|
647
670
|
path: z.string().describe("Where the declaration lives, relative to the workspace, whether or not the file exists yet."),
|
|
648
671
|
checks: z.array(RepoCheckSchema).describe("What it declares, in the order the file lists them."),
|
|
672
|
+
fired: z
|
|
673
|
+
.array(z.number().nullable())
|
|
674
|
+
.describe(
|
|
675
|
+
"When each declared check last reported something, in the file's order, as epoch milliseconds; null for one that never has, or for the land check, whose verdict the activity feed records instead.",
|
|
676
|
+
),
|
|
649
677
|
adopted: z
|
|
650
678
|
.boolean()
|
|
651
679
|
.describe("Whether these are running. False means declared and inert: nothing a repository writes runs until the owner switches it on."),
|
|
@@ -655,10 +683,16 @@ export const RepoChecksSummarySchema = z.object({
|
|
|
655
683
|
"Whether the declaration changed since it was adopted, which holds it until the owner looks again. True only for a repository that was adopted before.",
|
|
656
684
|
),
|
|
657
685
|
error: z.string().optional().describe("Why the file could not be read, when it exists but does not parse. The checks list is empty in that case."),
|
|
686
|
+
landDefault: z
|
|
687
|
+
.string()
|
|
688
|
+
.optional()
|
|
689
|
+
.describe(
|
|
690
|
+
"What runs on the main tree after a land when the file declares no `land` check: the package's own verify or test script. Absent when the repository has neither.",
|
|
691
|
+
),
|
|
658
692
|
});
|
|
659
693
|
export type RepoChecksSummary = z.infer<typeof RepoChecksSummarySchema>;
|
|
660
694
|
export const RepoChecksListSchema = z.object({
|
|
661
|
-
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks
|
|
695
|
+
repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks or has a package check that runs after a land, in id order."),
|
|
662
696
|
});
|
|
663
697
|
export type RepoChecksList = z.infer<typeof RepoChecksListSchema>;
|
|
664
698
|
export const RepoChecksAdoptSchema = z.object({
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { packageRoot } from "@intentic/constants/node";
|
|
4
|
-
import { test, expect } from "bun:test";
|
|
5
4
|
import { currentLock } from "./contract-lock.js";
|
|
6
5
|
|
|
7
6
|
// contract.lock.json must match the exported schemas; the git-aware half of this check lives in contract-shrink.mjs.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { test, expect } from "bun:test";
|
|
2
1
|
import { fixStance } from "./fix-stance.js";
|
|
3
2
|
import type { AgentSummary } from "../schemas/agents.js";
|
|
4
3
|
|
|
@@ -46,6 +45,20 @@ test("held work reads as a fix to review, whether the fleet called it ready or l
|
|
|
46
45
|
expect(fixStance(agent({ status: `idle`, diff: { files: 4, insertions: 120, deletions: 8 } })).kind).toBe(`ready`);
|
|
47
46
|
});
|
|
48
47
|
|
|
48
|
+
test.each([
|
|
49
|
+
["holding work", { files: 4, insertions: 120, deletions: 8 }],
|
|
50
|
+
["holding nothing yet", undefined],
|
|
51
|
+
] as const)("a turn that ended waiting on its own watch, %s, is still working", (_case, diff) => {
|
|
52
|
+
const watches = [{ id: `watch-a1b2`, note: `CI on the pushed branch`, intervalSeconds: 60, deadlineAt: 9_000 }];
|
|
53
|
+
expect(fixStance(agent({ status: `idle`, watches, ...(diff === undefined ? {} : { diff }) }))).toStrictEqual({
|
|
54
|
+
kind: `working`,
|
|
55
|
+
ongoing: true,
|
|
56
|
+
retry: false,
|
|
57
|
+
label: `Agent working`,
|
|
58
|
+
hint: `An agent is already working on this failure. Open the conversation to watch it.`,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
49
62
|
// Diff present doesn't change a failed status to ready; it only earns a mention in the hint.
|
|
50
63
|
test("an agent that failed after writing files still reads as failed", () => {
|
|
51
64
|
const stance = fixStance(agent({ status: `error`, diff: { files: 2, insertions: 34, deletions: 6 } }));
|
package/src/state/fix-stance.ts
CHANGED
|
@@ -130,6 +130,8 @@ const RULES: readonly ((agent: AgentSummary) => FixStance | undefined)[] = [
|
|
|
130
130
|
? needsYou("Needs you", "The fix agent has stopped and is waiting on you.")
|
|
131
131
|
: undefined,
|
|
132
132
|
(agent) => (IN_FLIGHT.has(agent.status) ? WORKING : undefined),
|
|
133
|
+
// An armed watch wakes it again by itself, so neither what it holds nor holding nothing is its last word yet.
|
|
134
|
+
(agent) => ((agent.watches?.length ?? 0) > 0 ? WORKING : undefined),
|
|
133
135
|
(agent) => (holdingWork(agent) ? READY : undefined),
|
|
134
136
|
(agent) => (agent.status === "landed" ? LANDED : undefined),
|
|
135
137
|
];
|
|
@@ -27,6 +27,10 @@ const RUNTIME_DOMAINS = [
|
|
|
27
27
|
// The agent's Chromiums and open pages, daemon-held, minted from its own browser tool-call hooks.
|
|
28
28
|
{ domain: "browsers", invalidates: [["browsers"]] },
|
|
29
29
|
|
|
30
|
+
// A connection's status as its last probe found it: pushed when a probe behind a served answer finds another one, and
|
|
31
|
+
// when a browser sign-in is saved or cleared, which is what moves an account's card off "log in".
|
|
32
|
+
{ domain: "capabilities", invalidates: [["capabilities"]] },
|
|
33
|
+
|
|
30
34
|
// Daemon-held; changes continuously (tokens, tool uses), so it's rate-limited rather than pushed per mutation.
|
|
31
35
|
{ domain: "subagents", invalidates: [["subagents"]] },
|
|
32
36
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { STATE_DIR } from "@intentic/constants";
|
|
2
2
|
import type { FileContribution } from "@intentic/extension-manifest";
|
|
3
|
-
import { describe, it, expect } from "bun:test";
|
|
4
3
|
import {
|
|
5
4
|
BACKED_UP_STATE_PATHS,
|
|
6
5
|
isLockedWorkspacePath,
|
|
@@ -346,8 +345,6 @@ describe(`VERSIONED_STATE_PATHS`, () => {
|
|
|
346
345
|
// Which commands count as heavy, and how many may run at once; a shared-box decision worth a `git log`
|
|
347
346
|
// line.
|
|
348
347
|
`${STATE_DIR}/config/heavy-commands.json`,
|
|
349
|
-
// The exclude list carves entries out by name; an unlisted directory is invisible to git and to land.
|
|
350
|
-
`${STATE_DIR}/config/hooks/`,
|
|
351
348
|
`${STATE_DIR}/config/loop-designs.json`,
|
|
352
349
|
`${STATE_DIR}/config/personas.json`,
|
|
353
350
|
// A persona's prompt and reachable skills: the text that decides how it behaves, same reason as its card.
|
|
@@ -120,20 +120,6 @@ const STATE_FILES = [
|
|
|
120
120
|
// Bash command. `carry`: a property of the workspace, not the machine. `versioned`: raising the limit is a decision
|
|
121
121
|
// worth a diff.
|
|
122
122
|
{ path: ".intentic/config/heavy-commands.json", invalidates: ["settings"], portability: "carry", versioned: true },
|
|
123
|
-
// Scripts the rule table runs, one per reader (settings.json points at them per event, e.g. `file.edited`).
|
|
124
|
-
// `versioned`: this is code that runs against every file an agent writes, more consequential than the rule naming
|
|
125
|
-
// it. `carry`: authored text, no credential. Invalidates nothing: the settings screen renders the rules from
|
|
126
|
-
// settings.json, not these scripts.
|
|
127
|
-
{
|
|
128
|
-
path: ".intentic/config/hooks/",
|
|
129
|
-
invalidates: [],
|
|
130
|
-
why: "The settings screen renders the rules that name these scripts, out of settings.json; nothing in the browser reads the scripts themselves.",
|
|
131
|
-
portability: "carry",
|
|
132
|
-
versioned: true,
|
|
133
|
-
// The daemon only runs these scripts by path; it never builds them, so there's no `statePath` call for the
|
|
134
|
-
// coverage guard to find.
|
|
135
|
-
outsideWriter: "the owner or an agent, authoring them; the daemon only ever RUNS one, by the path a rule's command names",
|
|
136
|
-
},
|
|
137
123
|
// Last-fired stamps for the rule table. `derived`: records what happened on this machine, not carried.
|
|
138
124
|
{
|
|
139
125
|
path: ".intentic/local/rule-firings.json",
|
package/src/text/embed.test.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { webcrypto } from "node:crypto";
|
|
2
|
-
import { test, expect, beforeAll, mock } from "bun:test";
|
|
3
2
|
import { stubGlobal, unstubAllGlobals } from "@intentic/testing/bun";
|
|
4
3
|
import { EmbedError, embedFailure, embedUrl, fetchEmbedJson, solveProofOfWork } from "./embed.js";
|
|
5
4
|
|
|
@@ -40,12 +39,12 @@ test("a refusal carries the server's own sentence and status, and a bodyless one
|
|
|
40
39
|
test("a JSON fetch answers the body or throws the refusal", async () => {
|
|
41
40
|
stubGlobal(
|
|
42
41
|
"fetch",
|
|
43
|
-
|
|
42
|
+
jest.fn(async () => new Response(JSON.stringify({ ok: true }), { status: 200 })),
|
|
44
43
|
);
|
|
45
44
|
expect(await fetchEmbedJson<{ ok: boolean }>("https://x/y")).toEqual({ ok: true });
|
|
46
45
|
stubGlobal(
|
|
47
46
|
"fetch",
|
|
48
|
-
|
|
47
|
+
jest.fn(async () => new Response(JSON.stringify({ error: "rate limited" }), { status: 429 })),
|
|
49
48
|
);
|
|
50
49
|
await expect(fetchEmbedJson("https://x/y")).rejects.toMatchObject({ message: "rate limited", status: 429 });
|
|
51
50
|
unstubAllGlobals();
|
package/src/text/emoji.test.ts
CHANGED
package/src/text/title.test.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { STATE_DIR } from "@intentic/constants";
|
|
2
|
-
import { describe, it, expect } from "bun:test";
|
|
3
2
|
import type { AgentEvent } from "../events/agent-events.js";
|
|
4
3
|
import type { TranscriptPatch, TranscriptRow } from "../events/transcript.js";
|
|
5
4
|
import { watchWakePrompt } from "../events/watch-wake.js";
|
|
@@ -213,9 +212,19 @@ describe("foldTurn", () => {
|
|
|
213
212
|
});
|
|
214
213
|
// An outage nobody armed says so, instead of promising the breaker's retry.
|
|
215
214
|
const unarmed: AgentEvent[] = [
|
|
216
|
-
{
|
|
215
|
+
{
|
|
216
|
+
kind: "error",
|
|
217
|
+
code: "provider-outage",
|
|
218
|
+
message: "Anthropic is down.",
|
|
219
|
+
autoResume: "available",
|
|
220
|
+
outage: { retryAt: 1 },
|
|
221
|
+
retries: { made: 0, max: 6 },
|
|
222
|
+
},
|
|
217
223
|
];
|
|
218
|
-
expect(foldOf("hi", unarmed).at(-1)).toEqual({
|
|
224
|
+
expect(foldOf("hi", unarmed).at(-1)).toEqual({
|
|
225
|
+
role: "notice",
|
|
226
|
+
text: "Anthropic is down. Nothing is retrying it, so the turn is waiting here.",
|
|
227
|
+
});
|
|
219
228
|
});
|
|
220
229
|
|
|
221
230
|
it("writes the turn's own events down as notices", () => {
|
|
@@ -430,7 +439,7 @@ describe("patches", () => {
|
|
|
430
439
|
"append",
|
|
431
440
|
"tool",
|
|
432
441
|
"tool",
|
|
433
|
-
"
|
|
442
|
+
"toolThinking",
|
|
434
443
|
"tool",
|
|
435
444
|
"replace",
|
|
436
445
|
"append",
|
|
@@ -442,6 +451,25 @@ describe("patches", () => {
|
|
|
442
451
|
]);
|
|
443
452
|
});
|
|
444
453
|
|
|
454
|
+
it("send a delegation's card without its subtree or thinking, and a client keeps the ones it has", () => {
|
|
455
|
+
const events: AgentEvent[] = [
|
|
456
|
+
{ kind: "tool_call", id: "task-1", name: "Agent", category: "other", status: "in_progress" },
|
|
457
|
+
{ kind: "tool_call", id: "t2", name: "Read", category: "read", status: "in_progress", parentToolUseId: "task-1" },
|
|
458
|
+
{ kind: "thinking", text: "in", parentToolUseId: "task-1" },
|
|
459
|
+
{ kind: "thinking", text: "ner", parentToolUseId: "task-1" },
|
|
460
|
+
{ kind: "tool_call_update", id: "task-1", status: "completed" },
|
|
461
|
+
];
|
|
462
|
+
const { folded, applied, patches } = replay(openingOf("go"), events);
|
|
463
|
+
expect(applied).toEqual(folded);
|
|
464
|
+
expect(patches.filter((patch) => patch.op === "toolThinking")).toEqual([
|
|
465
|
+
{ op: "toolThinking", index: 1, id: "task-1", text: "in" },
|
|
466
|
+
{ op: "toolThinking", index: 1, id: "task-1", text: "ner" },
|
|
467
|
+
]);
|
|
468
|
+
const update = patches.at(-1);
|
|
469
|
+
expect(update?.op === "tool" ? update.tool : undefined).toEqual({ id: "task-1", name: "Agent", category: "other", status: "completed" });
|
|
470
|
+
expect(applied[1]?.tools?.[0]).toMatchObject({ status: "completed", thinking: "inner", children: [{ id: "t2" }] });
|
|
471
|
+
});
|
|
472
|
+
|
|
445
473
|
// The other half of the wake's delivery: a live turn takes it as a steer. It is the daemon's own words either way,
|
|
446
474
|
// so it must not become a user row here, and must not leave a rewind anchor pointing at a message nobody sent.
|
|
447
475
|
it("writes a watch's wake into a live turn as a notice, and never as a steer to rewind to", () => {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type TranscriptTool,
|
|
12
12
|
} from "../events/transcript.js";
|
|
13
13
|
import { contextTrimLine } from "../schemas/context-trim.js";
|
|
14
|
+
import { keptWarmLine } from "../schemas/keep-warm.js";
|
|
14
15
|
import { turnedAwayCode } from "../policy/turned-away.js";
|
|
15
16
|
import { mentionedPathTokens } from "./mentions.js";
|
|
16
17
|
import { unspokenPromptRow } from "../events/agent-words.js";
|
|
@@ -21,6 +22,9 @@ import { unspokenPromptRow } from "../events/agent-words.js";
|
|
|
21
22
|
|
|
22
23
|
export type TurnEnding = "settled" | "stopped";
|
|
23
24
|
|
|
25
|
+
// A compaction's row, live and restored from the provider's store alike.
|
|
26
|
+
export const COMPACTED_NOTICE = "Context compacted to free up space.";
|
|
27
|
+
|
|
24
28
|
// Where a tool card lives: its row, and the parent card it nests under when it's a helper's own call.
|
|
25
29
|
interface CardPlace {
|
|
26
30
|
readonly tool: TranscriptTool;
|
|
@@ -32,6 +36,9 @@ interface CardPlace {
|
|
|
32
36
|
const defined = <T extends object>(value: T): Partial<T> =>
|
|
33
37
|
Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined)) as Partial<T>;
|
|
34
38
|
|
|
39
|
+
// A card's own fields: what a `tool` patch carries, so a delegation's growing subtree never rides one.
|
|
40
|
+
const ownFields = ({ children: _children, thinking: _thinking, ...own }: TranscriptTool): TranscriptTool => own;
|
|
41
|
+
|
|
35
42
|
const cardOf = (event: Extract<AgentEvent, { kind: "tool_call" }>): TranscriptTool => ({
|
|
36
43
|
id: event.id,
|
|
37
44
|
name: event.name,
|
|
@@ -296,7 +303,16 @@ export class TranscriptFold {
|
|
|
296
303
|
}
|
|
297
304
|
case "usage": {
|
|
298
305
|
// Lands on the last assistant bubble and closes it: a steered turn's stream can carry several turns.
|
|
299
|
-
const {
|
|
306
|
+
const {
|
|
307
|
+
kind: _kind,
|
|
308
|
+
account: _account,
|
|
309
|
+
cacheReadTokens: _read,
|
|
310
|
+
cacheCreationTokens: _written,
|
|
311
|
+
openingCacheReadTokens: _openingRead,
|
|
312
|
+
openingCacheCreationTokens: _openingWritten,
|
|
313
|
+
promptFingerprint: _fingerprint,
|
|
314
|
+
...usage
|
|
315
|
+
} = event;
|
|
300
316
|
const index = this.rows.findLastIndex((row) => row.role === "assistant");
|
|
301
317
|
const closed = this.closeBubble();
|
|
302
318
|
if (index === -1) {
|
|
@@ -318,6 +334,11 @@ export class TranscriptFold {
|
|
|
318
334
|
case "preamble":
|
|
319
335
|
// Collapses the daemon's preamble notes onto the user row; an empty note list is not a disclosure.
|
|
320
336
|
return event.notes.length === 0 ? [] : this.stampOpener((row) => (row.notes = [...event.notes]));
|
|
337
|
+
case "prompt_cache": {
|
|
338
|
+
// Only a conversation the sandbox kept warm gets a row: every other turn's cache is its own business.
|
|
339
|
+
const line = keptWarmLine(event);
|
|
340
|
+
return line === undefined ? [] : this.pushRow({ role: "notice", text: line });
|
|
341
|
+
}
|
|
321
342
|
case "context_trim":
|
|
322
343
|
// A row of its own rather than a stamp on the message: what was left out is not part of what was sent,
|
|
323
344
|
// and the fold beside the message only ever lists notes that actually rode.
|
|
@@ -327,7 +348,7 @@ export class TranscriptFold {
|
|
|
327
348
|
case "landed":
|
|
328
349
|
return this.pushRow(landedRow(event));
|
|
329
350
|
case "compact":
|
|
330
|
-
return this.pushRow({ role: "notice", text:
|
|
351
|
+
return this.pushRow({ role: "notice", text: COMPACTED_NOTICE });
|
|
331
352
|
case "error":
|
|
332
353
|
// Keeps a refusal (no prose from the provider) from reading as a session that ended mid-question.
|
|
333
354
|
// A refusal that ran nothing takes its message back out ahead of the notice standing in for it.
|
|
@@ -519,9 +540,8 @@ export class TranscriptFold {
|
|
|
519
540
|
return [];
|
|
520
541
|
}
|
|
521
542
|
if (event.kind === "thinking") {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
});
|
|
543
|
+
place.tool.thinking = `${place.tool.thinking ?? ""}${event.text}`;
|
|
544
|
+
return [{ op: "toolThinking", index: place.row, id: parent, text: event.text }];
|
|
525
545
|
}
|
|
526
546
|
if (event.kind === "tool_call") {
|
|
527
547
|
const child = cardOf(event);
|
|
@@ -590,7 +610,7 @@ export class TranscriptFold {
|
|
|
590
610
|
return [];
|
|
591
611
|
}
|
|
592
612
|
mutate(place.tool);
|
|
593
|
-
return [{ op: "tool", index: place.row, tool: structuredClone(place.tool), ...(place.parent === undefined ? {} : { parent: place.parent }) }];
|
|
613
|
+
return [{ op: "tool", index: place.row, tool: structuredClone(ownFields(place.tool)), ...(place.parent === undefined ? {} : { parent: place.parent }) }];
|
|
594
614
|
}
|
|
595
615
|
|
|
596
616
|
private stampOpener(mutate: (row: TranscriptRow) => void): TranscriptPatch[] {
|
|
@@ -637,15 +657,25 @@ export const applyTranscriptPatch = (rows: readonly TranscriptRow[], patch: Tran
|
|
|
637
657
|
return rows.map((row, index) => (index === patch.index ? { ...row, text: `${row.text}${patch.text}` } : row));
|
|
638
658
|
case "thinking":
|
|
639
659
|
return rows.map((row, index) => (index === patch.index ? { ...row, thinking: `${row.thinking ?? ""}${patch.text}` } : row));
|
|
660
|
+
case "toolThinking":
|
|
661
|
+
return rows.map((row, index) => (index === patch.index ? { ...row, tools: [...appendToolThinking(row.tools ?? [], patch.id, patch.text)] } : row));
|
|
640
662
|
case "tool":
|
|
641
663
|
return rows.map((row, index) => (index === patch.index ? { ...row, tools: upsertTool(row.tools ?? [], patch.tool, patch.parent) } : row));
|
|
642
664
|
}
|
|
643
665
|
};
|
|
644
666
|
|
|
645
|
-
//
|
|
646
|
-
|
|
667
|
+
// Appends a delegated subagent's reasoning onto the card with this id, wherever it nests.
|
|
668
|
+
export const appendToolThinking = (tools: readonly TranscriptTool[], id: string, text: string): readonly TranscriptTool[] =>
|
|
669
|
+
mapTool(tools, id, (card) => ({ ...card, thinking: `${card.thinking ?? ""}${text}` }));
|
|
670
|
+
|
|
671
|
+
// Replaces the tool with this id anywhere in the tree, keeping the nested calls and thinking it already holds, or appends
|
|
672
|
+
// it under `parent`, or at top level with no parent or no match.
|
|
647
673
|
export const upsertTool = (tools: readonly TranscriptTool[], tool: TranscriptTool, parent: string | undefined): TranscriptTool[] => {
|
|
648
|
-
const replaced = mapTool(tools, tool.id, () =>
|
|
674
|
+
const replaced = mapTool(tools, tool.id, (current) => ({
|
|
675
|
+
...tool,
|
|
676
|
+
...(current.children === undefined ? {} : { children: current.children }),
|
|
677
|
+
...(current.thinking === undefined ? {} : { thinking: current.thinking }),
|
|
678
|
+
}));
|
|
649
679
|
if (replaced !== tools) {
|
|
650
680
|
return [...replaced];
|
|
651
681
|
}
|
package/src/text/whisper.test.ts
CHANGED
package/src/time/zone.test.ts
CHANGED