@inhuman.tools/turn 0.1.0 → 0.3.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 -0
- package/dist/agent-calls/acceptance.d.ts +1 -1
- package/dist/agent-calls/acceptance.d.ts.map +1 -1
- package/dist/agent-calls/acceptance.js +1 -1
- package/dist/agent-calls/acceptance.js.map +1 -1
- package/dist/agent-calls/callbacks.d.ts +1 -1
- package/dist/agent-calls/callbacks.d.ts.map +1 -1
- package/dist/agent-calls/callbacks.js +1 -1
- package/dist/agent-calls/callbacks.js.map +1 -1
- package/dist/agent-calls/delivery.d.ts +1 -1
- package/dist/agent-calls/delivery.d.ts.map +1 -1
- package/dist/agent-calls/delivery.js +1 -1
- package/dist/agent-calls/delivery.js.map +1 -1
- package/dist/agent-calls/operator.d.ts +1 -1
- package/dist/agent-calls/operator.d.ts.map +1 -1
- package/dist/agent-calls/operator.js +1 -1
- package/dist/agent-calls/operator.js.map +1 -1
- package/dist/agent-calls/runtime.d.ts +4 -2
- package/dist/agent-calls/runtime.d.ts.map +1 -1
- package/dist/agent-calls/runtime.js +17 -11
- package/dist/agent-calls/runtime.js.map +1 -1
- package/dist/agent-calls/types.d.ts +1 -1
- package/dist/agent-calls/types.d.ts.map +1 -1
- package/dist/context/compact-floors.d.ts +1 -1
- package/dist/context/compact-floors.d.ts.map +1 -1
- package/dist/context/compact.d.ts +1 -1
- package/dist/context/compact.d.ts.map +1 -1
- package/dist/context/compact.js +5 -4
- package/dist/context/compact.js.map +1 -1
- package/dist/context/history.d.ts +1 -1
- package/dist/context/history.d.ts.map +1 -1
- package/dist/context/history.js +35 -3
- package/dist/context/history.js.map +1 -1
- package/dist/context/retention/defaults.js +1 -1
- package/dist/context/retention/defaults.js.map +1 -1
- package/dist/context/retention/resolve.js +1 -1
- package/dist/context/retention/resolve.js.map +1 -1
- package/dist/context/retention/schema.js +1 -1
- package/dist/context/retention/schema.js.map +1 -1
- package/dist/context/retention/stamp.d.ts +1 -1
- package/dist/context/retention/stamp.d.ts.map +1 -1
- package/dist/context/retention/stamp.js +5 -2
- package/dist/context/retention/stamp.js.map +1 -1
- package/dist/context/retention/step.d.ts +1 -1
- package/dist/context/retention/step.d.ts.map +1 -1
- package/dist/context/retention/step.js +7 -4
- package/dist/context/retention/step.js.map +1 -1
- package/dist/context/retention/view.d.ts +1 -1
- package/dist/context/retention/view.d.ts.map +1 -1
- package/dist/context/retention/view.js +1 -1
- package/dist/context/retention/view.js.map +1 -1
- package/dist/context/spawn-history.d.ts +13 -0
- package/dist/context/spawn-history.d.ts.map +1 -0
- package/dist/context/spawn-history.js +82 -0
- package/dist/context/spawn-history.js.map +1 -0
- package/dist/context/squeeze.d.ts +1 -1
- package/dist/context/squeeze.d.ts.map +1 -1
- package/dist/context/squeeze.js +1 -1
- package/dist/context/squeeze.js.map +1 -1
- package/dist/context/tools.d.ts +2 -2
- package/dist/context/tools.d.ts.map +1 -1
- package/dist/context/tools.js +2 -2
- package/dist/context/tools.js.map +1 -1
- package/dist/context/transcript.d.ts +3 -1
- package/dist/context/transcript.d.ts.map +1 -1
- package/dist/context/transcript.js +17 -4
- package/dist/context/transcript.js.map +1 -1
- package/dist/policy-processor/classifier.d.ts.map +1 -1
- package/dist/policy-processor/classifier.js +3 -1
- package/dist/policy-processor/classifier.js.map +1 -1
- package/dist/policy-processor/scope.d.ts +1 -0
- package/dist/policy-processor/scope.d.ts.map +1 -1
- package/dist/policy-processor/scope.js +9 -2
- package/dist/policy-processor/scope.js.map +1 -1
- package/dist/policy-processor/unattended-guard.js +8 -2
- package/dist/policy-processor/unattended-guard.js.map +1 -1
- package/dist/resources/compact-assets.d.ts +5 -0
- package/dist/resources/compact-assets.d.ts.map +1 -0
- package/dist/resources/compact-assets.js +6 -0
- package/dist/resources/compact-assets.js.map +1 -0
- package/dist/resources/compact-snapshot.d.ts +21 -0
- package/dist/resources/compact-snapshot.d.ts.map +1 -0
- package/dist/resources/compact-snapshot.js +44 -0
- package/dist/resources/compact-snapshot.js.map +1 -0
- package/dist/resources/index.d.ts +6 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +19 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/module-bundle.d.ts +16 -0
- package/dist/resources/module-bundle.d.ts.map +1 -0
- package/dist/resources/module-bundle.js +78 -0
- package/dist/resources/module-bundle.js.map +1 -0
- package/dist/runtime/ambient-compact.d.ts +38 -0
- package/dist/runtime/ambient-compact.d.ts.map +1 -0
- package/dist/runtime/ambient-compact.js +123 -0
- package/dist/runtime/ambient-compact.js.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/ports.d.ts +33 -0
- package/dist/runtime/ports.d.ts.map +1 -0
- package/dist/runtime/ports.js +2 -0
- package/dist/runtime/ports.js.map +1 -0
- package/dist/runtime/prompt-message-of.d.ts +4 -0
- package/dist/runtime/prompt-message-of.d.ts.map +1 -0
- package/dist/runtime/prompt-message-of.js +8 -0
- package/dist/runtime/prompt-message-of.js.map +1 -0
- package/dist/runtime/re-act-loop-intent-detected-trigger.d.ts +32 -0
- package/dist/runtime/re-act-loop-intent-detected-trigger.d.ts.map +1 -0
- package/dist/runtime/re-act-loop-intent-detected-trigger.js +132 -0
- package/dist/runtime/re-act-loop-intent-detected-trigger.js.map +1 -0
- package/dist/runtime/round-boundary.d.ts +13 -0
- package/dist/runtime/round-boundary.d.ts.map +1 -0
- package/dist/runtime/round-boundary.js +210 -0
- package/dist/runtime/round-boundary.js.map +1 -0
- package/dist/runtime/round-stop.d.ts +9 -0
- package/dist/runtime/round-stop.d.ts.map +1 -0
- package/dist/runtime/round-stop.js +19 -0
- package/dist/runtime/round-stop.js.map +1 -0
- package/dist/runtime/tool-call.d.ts +4 -0
- package/dist/runtime/tool-call.d.ts.map +1 -0
- package/dist/runtime/tool-call.js +8 -0
- package/dist/runtime/tool-call.js.map +1 -0
- package/dist/runtime/turn-iterator.d.ts +32 -0
- package/dist/runtime/turn-iterator.d.ts.map +1 -0
- package/dist/runtime/turn-iterator.js +251 -0
- package/dist/runtime/turn-iterator.js.map +1 -0
- package/dist/runtime/turn-resolvers.d.ts +17 -0
- package/dist/runtime/turn-resolvers.d.ts.map +1 -0
- package/dist/runtime/turn-resolvers.js +29 -0
- package/dist/runtime/turn-resolvers.js.map +1 -0
- package/dist/session/cancel.d.ts +4 -1
- package/dist/session/cancel.d.ts.map +1 -1
- package/dist/session/cancel.js +10 -2
- package/dist/session/cancel.js.map +1 -1
- package/dist/session/commands.d.ts +1 -1
- package/dist/session/commands.d.ts.map +1 -1
- package/dist/session/commands.js +4 -2
- package/dist/session/commands.js.map +1 -1
- package/dist/session/compact-actor.d.ts +12 -5
- package/dist/session/compact-actor.d.ts.map +1 -1
- package/dist/session/compact-actor.js +64 -12
- package/dist/session/compact-actor.js.map +1 -1
- package/dist/session/compact-env.d.ts +31 -5
- package/dist/session/compact-env.d.ts.map +1 -1
- package/dist/session/compact-env.js +23 -13
- package/dist/session/compact-env.js.map +1 -1
- package/dist/session/compact.d.ts +6 -3
- package/dist/session/compact.d.ts.map +1 -1
- package/dist/session/compact.js.map +1 -1
- package/dist/session/index.d.ts +2 -2
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -2
- package/dist/session/index.js.map +1 -1
- package/dist/session/park.d.ts +1 -1
- package/dist/session/park.d.ts.map +1 -1
- package/dist/session/park.js +1 -1
- package/dist/session/park.js.map +1 -1
- package/dist/session/side-channel-reminder.d.ts +1 -1
- package/dist/session/side-channel-reminder.d.ts.map +1 -1
- package/dist/session/side-channel-reminder.js +1 -1
- package/dist/session/side-channel-reminder.js.map +1 -1
- package/dist/session/types.d.ts +1 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/session-runtime/ask-line.d.ts +7 -0
- package/dist/session-runtime/ask-line.d.ts.map +1 -0
- package/dist/session-runtime/ask-line.js +36 -0
- package/dist/session-runtime/ask-line.js.map +1 -0
- package/dist/session-runtime/durability.d.ts +25 -0
- package/dist/session-runtime/durability.d.ts.map +1 -0
- package/dist/session-runtime/durability.js +18 -0
- package/dist/session-runtime/durability.js.map +1 -0
- package/dist/session-runtime/effects.d.ts +53 -0
- package/dist/session-runtime/effects.d.ts.map +1 -0
- package/dist/session-runtime/effects.js +18 -0
- package/dist/session-runtime/effects.js.map +1 -0
- package/dist/session-runtime/index.d.ts +8 -0
- package/dist/session-runtime/index.d.ts.map +1 -0
- package/dist/session-runtime/index.js +8 -0
- package/dist/session-runtime/index.js.map +1 -0
- package/dist/session-runtime/interrupt.d.ts +3 -0
- package/dist/session-runtime/interrupt.d.ts.map +1 -0
- package/dist/session-runtime/interrupt.js +33 -0
- package/dist/session-runtime/interrupt.js.map +1 -0
- package/dist/session-runtime/recovery.d.ts +17 -0
- package/dist/session-runtime/recovery.d.ts.map +1 -0
- package/dist/session-runtime/recovery.js +119 -0
- package/dist/session-runtime/recovery.js.map +1 -0
- package/dist/session-runtime/runtime.d.ts +50 -0
- package/dist/session-runtime/runtime.d.ts.map +1 -0
- package/dist/session-runtime/runtime.js +533 -0
- package/dist/session-runtime/runtime.js.map +1 -0
- package/dist/session-runtime/summary.d.ts +26 -0
- package/dist/session-runtime/summary.d.ts.map +1 -0
- package/dist/session-runtime/summary.js +33 -0
- package/dist/session-runtime/summary.js.map +1 -0
- package/dist/tool-catalog/bindings.d.ts +6 -0
- package/dist/tool-catalog/bindings.d.ts.map +1 -0
- package/dist/tool-catalog/bindings.js +37 -0
- package/dist/tool-catalog/bindings.js.map +1 -0
- package/dist/tool-catalog/catalog.d.ts +1 -3
- package/dist/tool-catalog/catalog.d.ts.map +1 -1
- package/dist/tool-catalog/catalog.js +6 -34
- package/dist/tool-catalog/catalog.js.map +1 -1
- package/dist/tool-catalog/types.d.ts +8 -0
- package/dist/tool-catalog/types.d.ts.map +1 -1
- package/dist/tool-catalog/types.js.map +1 -1
- package/dist/tool-runtime/approval/approved.d.ts +1 -1
- package/dist/tool-runtime/approval/approved.d.ts.map +1 -1
- package/dist/tool-runtime/approval/approved.js +1 -1
- package/dist/tool-runtime/approval/approved.js.map +1 -1
- package/dist/tool-runtime/handler.d.ts.map +1 -1
- package/dist/tool-runtime/handler.js +291 -237
- package/dist/tool-runtime/handler.js.map +1 -1
- package/dist/tool-runtime/run-system-tool.d.ts +1 -0
- package/dist/tool-runtime/run-system-tool.d.ts.map +1 -1
- package/dist/tool-runtime/run-system-tool.js +30 -10
- package/dist/tool-runtime/run-system-tool.js.map +1 -1
- package/dist/tool-runtime/worker-tools.d.ts.map +1 -1
- package/dist/tool-runtime/worker-tools.js +2 -2
- package/dist/tool-runtime/worker-tools.js.map +1 -1
- package/package.json +31 -18
- package/src/agent-calls/acceptance.ts +1 -1
- package/src/agent-calls/callbacks.ts +1 -1
- package/src/agent-calls/delivery.ts +1 -1
- package/src/agent-calls/operator.ts +7 -1
- package/src/agent-calls/runtime.ts +23 -11
- package/src/agent-calls/types.ts +1 -1
- package/src/context/compact-floors.ts +1 -1
- package/src/context/compact.ts +4 -5
- package/src/context/history.ts +46 -4
- package/src/context/retention/defaults.ts +1 -1
- package/src/context/retention/resolve.ts +1 -1
- package/src/context/retention/schema.ts +1 -1
- package/src/context/retention/stamp.ts +7 -2
- package/src/context/retention/step.ts +7 -4
- package/src/context/retention/view.ts +1 -1
- package/src/context/spawn-history.ts +95 -0
- package/src/context/squeeze.ts +1 -1
- package/src/context/tools.ts +3 -2
- package/src/context/transcript.ts +15 -4
- package/src/policy-processor/README.md +1 -1
- package/src/policy-processor/classifier.ts +3 -1
- package/src/policy-processor/scope.ts +7 -2
- package/src/policy-processor/unattended-guard.ts +5 -2
- package/src/resources/compact-assets.ts +9 -0
- package/src/resources/compact-snapshot.ts +51 -0
- package/src/resources/index.ts +27 -0
- package/src/resources/module-bundle.ts +90 -0
- package/src/runtime/ambient-compact.ts +147 -0
- package/src/runtime/index.ts +17 -0
- package/src/runtime/ports.ts +28 -0
- package/src/runtime/prompt-message-of.ts +14 -0
- package/src/runtime/re-act-loop-intent-detected-trigger.ts +177 -0
- package/src/runtime/round-boundary.ts +248 -0
- package/src/runtime/round-stop.ts +24 -0
- package/src/runtime/tool-call.ts +10 -0
- package/src/runtime/turn-iterator.ts +312 -0
- package/src/runtime/turn-resolvers.ts +43 -0
- package/src/session/cancel.ts +11 -3
- package/src/session/commands.ts +5 -2
- package/src/session/compact-actor.ts +76 -16
- package/src/session/compact-env.ts +27 -18
- package/src/session/compact.ts +8 -3
- package/src/session/index.ts +7 -2
- package/src/session/park.ts +7 -1
- package/src/session/side-channel-reminder.ts +1 -1
- package/src/session/types.ts +1 -1
- package/src/session-runtime/ask-line.ts +39 -0
- package/src/session-runtime/durability.ts +41 -0
- package/src/session-runtime/effects.ts +35 -0
- package/src/session-runtime/index.ts +19 -0
- package/src/session-runtime/interrupt.ts +34 -0
- package/src/session-runtime/recovery.ts +145 -0
- package/src/session-runtime/runtime.ts +535 -0
- package/src/session-runtime/summary.ts +67 -0
- package/src/tool-catalog/bindings.ts +37 -0
- package/src/tool-catalog/catalog.ts +6 -31
- package/src/tool-catalog/types.ts +9 -0
- package/src/tool-runtime/approval/approved.ts +1 -1
- package/src/tool-runtime/handler.ts +40 -24
- package/src/tool-runtime/run-system-tool.ts +27 -11
- package/src/tool-runtime/worker-tools.ts +2 -2
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { makeFsLoader, type Loader } from "@here.build/arrival-modules";
|
|
2
|
+
|
|
3
|
+
export type ResourceIdentity = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly revision: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ModuleBundle = ResourceIdentity & {
|
|
9
|
+
readonly loader: Loader;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ModuleBundleInput = ResourceIdentity & {
|
|
13
|
+
readonly modules: Readonly<Record<string, string>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Pins each resolved read, including failures; the host may persist it before evaluation proceeds. */
|
|
17
|
+
export function pinModuleLoader(
|
|
18
|
+
loader: Loader,
|
|
19
|
+
capture?: (path: string, content: string) => void | Promise<void>,
|
|
20
|
+
): Loader {
|
|
21
|
+
const reads = new Map<string, Promise<string>>();
|
|
22
|
+
return {
|
|
23
|
+
resolve: (specifier, fromDir) => loader.resolve(specifier, fromDir),
|
|
24
|
+
read(path) {
|
|
25
|
+
let read = reads.get(path);
|
|
26
|
+
if (read === undefined) {
|
|
27
|
+
read = Promise.resolve()
|
|
28
|
+
.then(() => loader.read(path))
|
|
29
|
+
.then(async (content) => {
|
|
30
|
+
const text = typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
31
|
+
await capture?.(path, text);
|
|
32
|
+
return text;
|
|
33
|
+
});
|
|
34
|
+
reads.set(path, read);
|
|
35
|
+
}
|
|
36
|
+
return read;
|
|
37
|
+
},
|
|
38
|
+
resolvers: loader.resolvers,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function moduleKey(path: string): string {
|
|
43
|
+
if (path.startsWith("/") || /[\\\0]/.test(path) || /^[a-z][a-z\d+.-]*:/i.test(path)) {
|
|
44
|
+
throw new Error(`module is outside the resource namespace: ${path}`);
|
|
45
|
+
}
|
|
46
|
+
const segments: string[] = [];
|
|
47
|
+
for (const segment of path.split("/")) {
|
|
48
|
+
if (segment === "" || segment === ".") continue;
|
|
49
|
+
if (segment === "..") {
|
|
50
|
+
if (segments.length === 0) throw new Error(`module escapes the resource namespace: ${path}`);
|
|
51
|
+
segments.pop();
|
|
52
|
+
} else {
|
|
53
|
+
segments.push(segment);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (segments.length === 0) throw new Error("module key is empty");
|
|
57
|
+
return segments.join("/");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Captures content once; nested imports can only read this bundle's normalized keys. */
|
|
61
|
+
export function createModuleBundle(input: ModuleBundleInput): ModuleBundle {
|
|
62
|
+
if (input.id.trim() === "" || input.revision.trim() === "") {
|
|
63
|
+
throw new Error("a module bundle requires an identity and revision");
|
|
64
|
+
}
|
|
65
|
+
const modules = new Map<string, string>();
|
|
66
|
+
for (const [path, content] of Object.entries(input.modules)) {
|
|
67
|
+
const key = moduleKey(path);
|
|
68
|
+
if (modules.has(key)) throw new Error(`duplicate normalized module: ${key}`);
|
|
69
|
+
modules.set(key, content);
|
|
70
|
+
}
|
|
71
|
+
const base = makeFsLoader({
|
|
72
|
+
readFile(path) {
|
|
73
|
+
const key = moduleKey(path);
|
|
74
|
+
const content = modules.get(key);
|
|
75
|
+
if (content === undefined) throw new Error(`module unavailable in ${input.id}@${input.revision}: ${key}`);
|
|
76
|
+
return content;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const loader: Loader = {
|
|
80
|
+
resolve(specifier, fromDir) {
|
|
81
|
+
if (specifier.startsWith("/") || /[\\\0]/.test(specifier) || /^[a-z][a-z\d+.-]*:/i.test(specifier)) {
|
|
82
|
+
throw new Error(`module is outside the resource namespace: ${specifier}`);
|
|
83
|
+
}
|
|
84
|
+
return base.resolve(specifier, fromDir);
|
|
85
|
+
},
|
|
86
|
+
read: (path) => base.read(path),
|
|
87
|
+
resolvers: base.resolvers,
|
|
88
|
+
};
|
|
89
|
+
return Object.freeze({ id: input.id, revision: input.revision, loader });
|
|
90
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient compact is a parallel isolated completion, not a tail splice.
|
|
3
|
+
* The turn does not wait.
|
|
4
|
+
*
|
|
5
|
+
* Who reads the summary: nothing in the product. `runCompactJob` and the
|
|
6
|
+
* compaction program build their own card from the full prefix and never
|
|
7
|
+
* consult it; the only thing read back is `ambientCompactKey` on the cutoff
|
|
8
|
+
* round, which stops the same prefix from starting a second summarizer. The
|
|
9
|
+
* summary is kept as the isolated completion's `responseText` so that a reader
|
|
10
|
+
* (a future cheap-cutoff path) has something to find. Until one exists the run
|
|
11
|
+
* is deliberately minimal: it holds the prefix text only while the request is
|
|
12
|
+
* in flight, then keeps the result and a reference, and completes.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
CompletionExpectation,
|
|
16
|
+
CompletionJob,
|
|
17
|
+
PromptMessage,
|
|
18
|
+
type SessionTurn,
|
|
19
|
+
} from "@inhuman.tools/harness-model/session";
|
|
20
|
+
import type { CompleteRequest, CompleteResult } from "@inhuman.tools/harness-provider-sdk";
|
|
21
|
+
|
|
22
|
+
import type { PromptMessageFactory, RuntimeDiagnostics } from "./ports.js";
|
|
23
|
+
import { promptMessageOf } from "./prompt-message-of.js";
|
|
24
|
+
import { textOfCell, Transcript } from "../context/index.js";
|
|
25
|
+
|
|
26
|
+
const SNAPSHOT_SYSTEM = `Fold this conversation prefix into a reference snapshot. Keep decisions, constraints, errors, and unresolved work. Drop tool chatter the snapshot does not need. Do not address the reader. Do not list next steps.`;
|
|
27
|
+
|
|
28
|
+
function kvTrim(kvs: Readonly<Record<string, string>>, key: string): string {
|
|
29
|
+
if (!Object.hasOwn(kvs, key)) return "";
|
|
30
|
+
return kvs[key].trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function ambientModel(kvs: Readonly<Record<string, string>>, fallback: string): string {
|
|
34
|
+
const named = kvTrim(kvs, "models.summarizer") || kvTrim(kvs, ":models.summarizer");
|
|
35
|
+
return named !== "" ? named : fallback.trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** First kept round. The summary of everything older hangs here. */
|
|
39
|
+
export function ambientCutoff(keep: Transcript): CompletionJob | null {
|
|
40
|
+
for (const cell of keep.cells) {
|
|
41
|
+
if (cell instanceof CompletionJob) return cell;
|
|
42
|
+
if (cell instanceof CompletionExpectation && cell.parent instanceof CompletionJob) return cell.parent;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function ambientKey(prefix: Transcript, focus: string): string {
|
|
48
|
+
const head = textOfCell(prefix.cells[0]).slice(0, 80);
|
|
49
|
+
const tail = textOfCell(prefix.cells.at(-1)).slice(0, 80);
|
|
50
|
+
return `${prefix.cells.length}\0${prefix.tokensUsed()}\0${head}\0${tail}\0${focus}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fire a summarizer beside the turn. Returns the settlement promise so a test
|
|
55
|
+
* can observe it; the turn loop must not await it.
|
|
56
|
+
*/
|
|
57
|
+
export function startAmbientCompact(opts: {
|
|
58
|
+
readonly home: SessionTurn;
|
|
59
|
+
readonly cutoff: CompletionJob;
|
|
60
|
+
readonly prefix: Transcript;
|
|
61
|
+
readonly focus: string;
|
|
62
|
+
readonly connectionId: string;
|
|
63
|
+
readonly model: string;
|
|
64
|
+
readonly complete: (req: CompleteRequest) => Promise<CompleteResult>;
|
|
65
|
+
readonly inflight: Map<CompletionJob, string>;
|
|
66
|
+
readonly message?: PromptMessageFactory;
|
|
67
|
+
readonly diagnostics?: RuntimeDiagnostics;
|
|
68
|
+
}): Promise<void> {
|
|
69
|
+
const key = ambientKey(opts.prefix, opts.focus);
|
|
70
|
+
if (opts.cutoff.ambientCompactKey === key) return Promise.resolve();
|
|
71
|
+
if (opts.inflight.get(opts.cutoff) === key) return Promise.resolve();
|
|
72
|
+
const transcript = opts.prefix.cells
|
|
73
|
+
.map((cell) => textOfCell(cell))
|
|
74
|
+
.filter((line) => line !== "")
|
|
75
|
+
.join("\n\n");
|
|
76
|
+
if (transcript === "" || opts.connectionId === "" || opts.model === "") return Promise.resolve();
|
|
77
|
+
opts.inflight.set(opts.cutoff, key);
|
|
78
|
+
return runAmbientCompact(opts, key, transcript);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function runAmbientCompact(
|
|
82
|
+
opts: {
|
|
83
|
+
readonly home: SessionTurn;
|
|
84
|
+
readonly cutoff: CompletionJob;
|
|
85
|
+
readonly focus: string;
|
|
86
|
+
readonly connectionId: string;
|
|
87
|
+
readonly model: string;
|
|
88
|
+
readonly complete: (req: CompleteRequest) => Promise<CompleteResult>;
|
|
89
|
+
readonly inflight: Map<CompletionJob, string>;
|
|
90
|
+
readonly message?: PromptMessageFactory;
|
|
91
|
+
readonly diagnostics?: RuntimeDiagnostics;
|
|
92
|
+
},
|
|
93
|
+
key: string,
|
|
94
|
+
transcript: string,
|
|
95
|
+
): Promise<void> {
|
|
96
|
+
const user = opts.focus === "" ? transcript : `${transcript}\n\nEmphasize:\n${opts.focus}`;
|
|
97
|
+
let job: CompletionJob | null = null;
|
|
98
|
+
try {
|
|
99
|
+
const round = new CompletionExpectation({
|
|
100
|
+
connectionId: opts.connectionId,
|
|
101
|
+
model: opts.model,
|
|
102
|
+
completionMode: "isolated",
|
|
103
|
+
reasoningEffort: "none",
|
|
104
|
+
// The transcript prefix is the whole conversation again: past the inline limit it lives in the blob
|
|
105
|
+
// bucket and the document keeps its head.
|
|
106
|
+
promptMessages: [
|
|
107
|
+
(opts.message ?? promptMessageOf)("system", SNAPSHOT_SYSTEM),
|
|
108
|
+
(opts.message ?? promptMessageOf)("user", user),
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
job = new CompletionJob({ expectation: round });
|
|
112
|
+
opts.home.pushChild(job);
|
|
113
|
+
await opts.complete({
|
|
114
|
+
connectionId: opts.connectionId,
|
|
115
|
+
model: opts.model,
|
|
116
|
+
promptText: round.userPrompt,
|
|
117
|
+
round,
|
|
118
|
+
});
|
|
119
|
+
if (opts.inflight.get(opts.cutoff) !== key) return;
|
|
120
|
+
const text = round.responseText.trim();
|
|
121
|
+
if (round.outcome !== "successful" || text === "") return;
|
|
122
|
+
// The summary stays on the isolated completion (the result). The key is what
|
|
123
|
+
// stops the next round with this prefix from starting another summarizer.
|
|
124
|
+
opts.cutoff.ambientCompactKey = key;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (opts.inflight.get(opts.cutoff) === key) opts.diagnostics?.fail?.("ambient compact", error);
|
|
127
|
+
} finally {
|
|
128
|
+
if (job != null) settleAmbientJob(job, key, opts.focus);
|
|
129
|
+
if (opts.inflight.get(opts.cutoff) === key) opts.inflight.delete(opts.cutoff);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A settled ambient run keeps its result and a reference to what it summarized,
|
|
135
|
+
* not the copy of the prefix it was asked about: that copy is the whole
|
|
136
|
+
* conversation again and nothing reads it. The job is then complete, however
|
|
137
|
+
* the run ended (a newer prefix winning counts), so it is not left open.
|
|
138
|
+
*/
|
|
139
|
+
function settleAmbientJob(job: CompletionJob, key: string, focus: string): void {
|
|
140
|
+
const round = job.expectation;
|
|
141
|
+
if (!round.isDone) return;
|
|
142
|
+
const [cells = "?", tokens = "?"] = key.split("\0");
|
|
143
|
+
const ref = [`ambient compact of a prefix of ${cells} cells, ${tokens} tokens`];
|
|
144
|
+
if (focus !== "") ref.push(`focus: ${focus}`);
|
|
145
|
+
round.promptMessages = [new PromptMessage({ role: "user", content: ref.join("\n") })];
|
|
146
|
+
if (!job.completed) job.markComplete();
|
|
147
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { iterateTurn, type TurnIteratorHost } from "./turn-iterator.js";
|
|
2
|
+
export { createTurnResolvers, type TurnResolverHost, type TurnResolvers } from "./turn-resolvers.js";
|
|
3
|
+
export {
|
|
4
|
+
type RuntimeLoopNote,
|
|
5
|
+
type RuntimeNoteLog,
|
|
6
|
+
type TurnSettle,
|
|
7
|
+
type RuntimeDiagnostics,
|
|
8
|
+
type PromptTextStamp,
|
|
9
|
+
type PromptTextExternalizer,
|
|
10
|
+
type PromptMessageFactory,
|
|
11
|
+
} from "./ports.js";
|
|
12
|
+
export { promptMessageOf } from "./prompt-message-of.js";
|
|
13
|
+
export { ambientCutoff, ambientKey, ambientModel, startAmbientCompact } from "./ambient-compact.js";
|
|
14
|
+
export * from "./re-act-loop-intent-detected-trigger.js";
|
|
15
|
+
export { CONTENT_FILTER_STOP, LENGTH_STOP, refusalText, maxRoundsMessage, roundCapReached } from "./round-stop.js";
|
|
16
|
+
|
|
17
|
+
export * from "../session-runtime/index.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PromptMessage, PromptMessageRole } from "@inhuman.tools/harness-model/session";
|
|
2
|
+
|
|
3
|
+
export type RuntimeLoopNote = {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly mode: "ambient" | "activating";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export interface RuntimeNoteLog {
|
|
10
|
+
peek(sessionId: string): readonly RuntimeLoopNote[];
|
|
11
|
+
consume(sessionId: string, ids: readonly string[], callId: string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TurnSettle =
|
|
15
|
+
{ readonly ok: true; readonly value: string | false } | { readonly ok: false; readonly error: string };
|
|
16
|
+
|
|
17
|
+
export interface RuntimeDiagnostics {
|
|
18
|
+
step?(message: string): void;
|
|
19
|
+
trace?(namespace: string, message: string): void;
|
|
20
|
+
fail?(label: string, error: unknown): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type PromptTextStamp =
|
|
24
|
+
| { readonly kind: "inline"; readonly text: string }
|
|
25
|
+
| { readonly kind: "blob"; readonly head: string; readonly blobJson: string };
|
|
26
|
+
|
|
27
|
+
export type PromptTextExternalizer = (text: string) => PromptTextStamp;
|
|
28
|
+
export type PromptMessageFactory = (role: PromptMessageRole, text: string) => PromptMessage;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PromptMessage, type PromptMessageRole } from "@inhuman.tools/harness-model/session";
|
|
2
|
+
|
|
3
|
+
import type { PromptTextExternalizer } from "./ports.js";
|
|
4
|
+
|
|
5
|
+
export function promptMessageOf(
|
|
6
|
+
role: PromptMessageRole,
|
|
7
|
+
text: string,
|
|
8
|
+
externalize?: PromptTextExternalizer,
|
|
9
|
+
): PromptMessage {
|
|
10
|
+
const stamp = externalize?.(text) ?? { kind: "inline", text };
|
|
11
|
+
return stamp.kind === "inline"
|
|
12
|
+
? new PromptMessage({ role, content: stamp.text })
|
|
13
|
+
: new PromptMessage({ role, content: stamp.head, contentBlobJson: stamp.blobJson });
|
|
14
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-only continuation intern (A). Isolated json_schema, judge wire,
|
|
3
|
+
* one shot. Returns a typed assessment, including unavailability; it never
|
|
4
|
+
* authorizes a model invocation itself. The round boundary owns arbitration.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
clipSpeech,
|
|
8
|
+
INTERN_SYSTEM,
|
|
9
|
+
LENGTH_CONTINUE_NOTE as CORE_LENGTH_NOTE,
|
|
10
|
+
RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_NOTE as CORE_RE_ACT_NOTE,
|
|
11
|
+
RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_TIMEOUT_MS,
|
|
12
|
+
THOUGHT_CONTINUE_NOTE as CORE_THOUGHT_NOTE,
|
|
13
|
+
verdictOf,
|
|
14
|
+
} from "@inhuman.tools/continuation";
|
|
15
|
+
import { prompt } from "@inhuman.tools/harness-commons";
|
|
16
|
+
import {
|
|
17
|
+
CompletionExpectation,
|
|
18
|
+
type IntentAssessment,
|
|
19
|
+
CompletionJob,
|
|
20
|
+
flattenOverviewLine,
|
|
21
|
+
GLANCE_ASK_LINE_MAX_CHARS,
|
|
22
|
+
isJsonSchemaUnsupported,
|
|
23
|
+
SessionTurn,
|
|
24
|
+
} from "@inhuman.tools/harness-model/session";
|
|
25
|
+
import {
|
|
26
|
+
parseTextOnlyContinueVote,
|
|
27
|
+
RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_RESPONSE_SCHEMA,
|
|
28
|
+
} from "@inhuman.tools/harness-model/shared";
|
|
29
|
+
import type { CompleteRequest, CompleteResult } from "@inhuman.tools/harness-provider-sdk";
|
|
30
|
+
import { splitThink } from "@inhuman.tools/tool-call-normalize";
|
|
31
|
+
|
|
32
|
+
import type { PromptMessageFactory } from "./ports.js";
|
|
33
|
+
import { promptMessageOf } from "./prompt-message-of.js";
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
clipSpeech,
|
|
37
|
+
INTERN_SYSTEM,
|
|
38
|
+
RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_SPEECH_CHARS,
|
|
39
|
+
RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_TIMEOUT_MS,
|
|
40
|
+
THOUGHT_WITHOUT_ANSWER_STREAK_MAX,
|
|
41
|
+
verdictOf,
|
|
42
|
+
type ReActLoopIntentDetectedTriggerVerdict,
|
|
43
|
+
} from "@inhuman.tools/continuation";
|
|
44
|
+
|
|
45
|
+
export const RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_NOTE = prompt.system`${CORE_RE_ACT_NOTE}`;
|
|
46
|
+
|
|
47
|
+
export const THOUGHT_CONTINUE_NOTE = prompt.system`${CORE_THOUGHT_NOTE}`;
|
|
48
|
+
|
|
49
|
+
export const LENGTH_CONTINUE_NOTE = prompt.system`${CORE_LENGTH_NOTE}`;
|
|
50
|
+
|
|
51
|
+
export type InternJudgeWire = {
|
|
52
|
+
readonly connection: string;
|
|
53
|
+
readonly model: string;
|
|
54
|
+
readonly key: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export function internJudgeWire(
|
|
58
|
+
kvs: Readonly<Record<string, string>>,
|
|
59
|
+
fallback: Readonly<Record<string, string>>,
|
|
60
|
+
configuredModel: (connectionId: string) => string,
|
|
61
|
+
): InternJudgeWire | null {
|
|
62
|
+
const connection = (kvs.judgeConnection || fallback.judgeConnection || "").trim();
|
|
63
|
+
const named = (kvs.judgeModel || fallback.judgeModel || "").trim();
|
|
64
|
+
const configured = connection !== "" ? configuredModel(connection).trim() : "";
|
|
65
|
+
const model = (named || configured).trim();
|
|
66
|
+
if (connection === "" || model === "") return null;
|
|
67
|
+
return { connection, model, key: `${connection}\n${model}` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function questionLineFromSpeech(text: string): string {
|
|
71
|
+
const line = flattenOverviewLine(splitThink(text).content, GLANCE_ASK_LINE_MAX_CHARS);
|
|
72
|
+
return line === "" ? "has a question" : line;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A thinking channel with no spoken answer. The round has not finished.
|
|
77
|
+
* Visible CoT, a think-tag that is the whole message, encrypted reasoning,
|
|
78
|
+
* billed reasoning tokens, or a stamped thought kind (redacted / empty
|
|
79
|
+
* thinking part) are the same event: thought without an answer.
|
|
80
|
+
*/
|
|
81
|
+
export function thoughtWithoutAnswer(pew: CompletionExpectation): boolean {
|
|
82
|
+
const split = splitThink(pew.responseText);
|
|
83
|
+
if (split.content.trim() !== "") return false;
|
|
84
|
+
if (`${pew.responseThought}\n${split.reasoning}`.trim() !== "") return true;
|
|
85
|
+
if (pew.responseReasoningEncrypted.trim() !== "") return true;
|
|
86
|
+
if ((pew.reasoningTokens ?? 0) > 0) return true;
|
|
87
|
+
return pew.responseThoughtKind != null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ReActLoopIntentDetectedTriggerComplete = (req: CompleteRequest) => Promise<CompleteResult>;
|
|
91
|
+
|
|
92
|
+
export async function assessTextOnlyContinueIntern(opts: {
|
|
93
|
+
readonly speech: string;
|
|
94
|
+
readonly wire: InternJudgeWire;
|
|
95
|
+
readonly haltKeys: Set<string>;
|
|
96
|
+
readonly home: SessionTurn;
|
|
97
|
+
readonly complete: ReActLoopIntentDetectedTriggerComplete;
|
|
98
|
+
readonly signal: AbortSignal;
|
|
99
|
+
readonly message?: PromptMessageFactory;
|
|
100
|
+
}): Promise<IntentAssessment> {
|
|
101
|
+
if (opts.haltKeys.has(opts.wire.key))
|
|
102
|
+
return { verdict: null, status: "unsupported", detail: "Continuation judge does not support structured output." };
|
|
103
|
+
const speech = clipSpeech(splitThink(opts.speech).content);
|
|
104
|
+
const intern = new CompletionExpectation({
|
|
105
|
+
connectionId: opts.wire.connection,
|
|
106
|
+
model: opts.wire.model,
|
|
107
|
+
completionMode: "isolated",
|
|
108
|
+
reasoningEffort: "none",
|
|
109
|
+
promptMessages: [
|
|
110
|
+
(opts.message ?? promptMessageOf)("system", INTERN_SYSTEM),
|
|
111
|
+
(opts.message ?? promptMessageOf)("user", speech),
|
|
112
|
+
],
|
|
113
|
+
responseSchema: RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_RESPONSE_SCHEMA,
|
|
114
|
+
});
|
|
115
|
+
const job = new CompletionJob({ expectation: intern });
|
|
116
|
+
opts.home.pushChild(job);
|
|
117
|
+
const internAbort = AbortSignal.any([
|
|
118
|
+
opts.signal,
|
|
119
|
+
AbortSignal.timeout(RE_ACT_LOOP_INTENT_DETECTED_TRIGGER_TIMEOUT_MS),
|
|
120
|
+
]);
|
|
121
|
+
let stopWaiting = (): void => {};
|
|
122
|
+
try {
|
|
123
|
+
internAbort.throwIfAborted();
|
|
124
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
125
|
+
const onAbort = (): void => reject(internAbort.reason ?? new Error("Continuation judge aborted."));
|
|
126
|
+
stopWaiting = () => internAbort.removeEventListener("abort", onAbort);
|
|
127
|
+
if (internAbort.aborted) onAbort();
|
|
128
|
+
else internAbort.addEventListener("abort", onAbort, { once: true });
|
|
129
|
+
});
|
|
130
|
+
await Promise.race([
|
|
131
|
+
opts.complete({
|
|
132
|
+
connectionId: opts.wire.connection,
|
|
133
|
+
model: opts.wire.model,
|
|
134
|
+
promptText: intern.userPrompt,
|
|
135
|
+
responseSchemaJson: intern.responseSchema == null ? undefined : JSON.stringify(intern.responseSchema),
|
|
136
|
+
round: intern,
|
|
137
|
+
signal: internAbort,
|
|
138
|
+
}),
|
|
139
|
+
aborted,
|
|
140
|
+
]);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
if (isJsonSchemaUnsupported(message)) opts.haltKeys.add(opts.wire.key);
|
|
144
|
+
if (intern.outcome == null) {
|
|
145
|
+
intern.failReason = message;
|
|
146
|
+
intern.fail("crash", message, "null");
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
verdict: null,
|
|
150
|
+
status: isJsonSchemaUnsupported(message)
|
|
151
|
+
? "unsupported"
|
|
152
|
+
: message.includes("response schema:")
|
|
153
|
+
? "invalid"
|
|
154
|
+
: "failed",
|
|
155
|
+
detail: message,
|
|
156
|
+
};
|
|
157
|
+
} finally {
|
|
158
|
+
stopWaiting();
|
|
159
|
+
job.completed = true;
|
|
160
|
+
}
|
|
161
|
+
if (intern.outcome !== "successful") {
|
|
162
|
+
const detail = intern.failReason?.trim() || intern.endDetail?.trim() || "Continuation judge failed.";
|
|
163
|
+
if (isJsonSchemaUnsupported(detail)) opts.haltKeys.add(opts.wire.key);
|
|
164
|
+
return {
|
|
165
|
+
verdict: null,
|
|
166
|
+
status: isJsonSchemaUnsupported(detail)
|
|
167
|
+
? "unsupported"
|
|
168
|
+
: detail.includes("response schema:")
|
|
169
|
+
? "invalid"
|
|
170
|
+
: "failed",
|
|
171
|
+
detail,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const vote = parseTextOnlyContinueVote(intern.responseText);
|
|
175
|
+
if (vote == null) return { verdict: null, status: "invalid", detail: "Continuation judge returned an invalid vote." };
|
|
176
|
+
return { verdict: verdictOf(vote), status: "judged", detail: "" };
|
|
177
|
+
}
|