@ai-ecoverse/slicc-agent 4.2.0 → 4.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.
@@ -0,0 +1,207 @@
1
+ import { replicatedState } from "@earendil-works/chord";
2
+ import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
3
+ import {
4
+ defineDoc,
5
+ UsageDoc
6
+ } from "@earendil-works/pi-durable";
7
+ import { answerText } from "../agent.js";
8
+ import { vaultHold } from "../secrets/view.js";
9
+ import { scrubbed } from "./generate.js";
10
+ import { labelKey, shownLabel } from "./shown.js";
11
+ const LabelsDoc = defineDoc({
12
+ kind: "slicc.labels",
13
+ version: 1,
14
+ scope: "session",
15
+ initial: () => ({ labels: {} })
16
+ });
17
+ const LABELS_KEPT = 500;
18
+ const LABELS_PER_MINUTE = 6;
19
+ const MINUTE = 6e4;
20
+ const LABEL_WORDS = 8;
21
+ const LABEL_LENGTH = 80;
22
+ const OUTPUT_LENGTH = 600;
23
+ const LABEL_WAIT_MS = 3e4;
24
+ function callIdsOf(views) {
25
+ const ids = /* @__PURE__ */ new Set();
26
+ for (const view of views)
27
+ for (const entry of view.entries) {
28
+ const content = entry.model?.[0]?.content;
29
+ if (entry.kind === "pi.assistant" && Array.isArray(content)) {
30
+ for (const block of content)
31
+ if (block.type === "toolCall" && block.id) ids.add(block.id);
32
+ }
33
+ }
34
+ return ids;
35
+ }
36
+ function visibleLabels(labels, sources, views) {
37
+ const pick = () => {
38
+ const ids = callIdsOf(views());
39
+ return Object.fromEntries(
40
+ Object.entries(labels.value).filter(
41
+ ([key]) => key.split(" ").every((id) => ids.has(id))
42
+ )
43
+ );
44
+ };
45
+ const state = replicatedState(pick());
46
+ let queued = false;
47
+ const update = () => {
48
+ if (queued) return;
49
+ queued = true;
50
+ queueMicrotask(() => {
51
+ queued = false;
52
+ const next = pick();
53
+ const before = state.value;
54
+ const keys = Object.keys(next);
55
+ if (keys.length !== Object.keys(before).length || keys.some((key) => before[key] !== next[key]))
56
+ state.replace(BACKGROUND_CONTEXT, next);
57
+ });
58
+ };
59
+ const offs = [labels, ...sources].map((source) => source.subscribe(update));
60
+ return {
61
+ state,
62
+ dispose() {
63
+ for (const off of offs) off();
64
+ }
65
+ };
66
+ }
67
+ function watchLabel(labels, waitMs = LABEL_WAIT_MS) {
68
+ return (_agentId, callIds) => {
69
+ const found = shownLabel(labels.value, callIds);
70
+ if (found !== void 0 || !callIds.length) return Promise.resolve(found ?? null);
71
+ return new Promise((resolve) => {
72
+ const done = (label) => {
73
+ clearTimeout(timer);
74
+ off();
75
+ resolve(label);
76
+ };
77
+ const timer = setTimeout(() => done(null), waitMs);
78
+ const off = labels.subscribe(() => {
79
+ const label = shownLabel(labels.value, callIds);
80
+ if (label !== void 0) done(label);
81
+ });
82
+ });
83
+ };
84
+ }
85
+ function cleanLabel(text) {
86
+ return text.replace(/\s+/g, " ").trim().replace(/^["'“”]+|["'“”]+$/g, "").replace(/\.+$/, "").trim().split(" ").slice(0, LABEL_WORDS).join(" ").slice(0, LABEL_LENGTH);
87
+ }
88
+ function clusterOf(entries, callIds) {
89
+ const calls = /* @__PURE__ */ new Map();
90
+ const results = /* @__PURE__ */ new Map();
91
+ for (const entry of entries) {
92
+ const said = entry.model?.[0];
93
+ if (entry.kind === "pi.assistant" && Array.isArray(said?.content)) {
94
+ for (const block of said.content)
95
+ if (block.type === "toolCall" && block.id)
96
+ calls.set(block.id, {
97
+ name: block.name ?? "tool",
98
+ input: JSON.stringify(block.arguments ?? {})
99
+ });
100
+ }
101
+ if (entry.kind === "pi.tool-result" && said?.toolCallId)
102
+ results.set(said.toolCallId, {
103
+ output: answerText(said.content),
104
+ at: said.timestamp ?? 0
105
+ });
106
+ }
107
+ const runs = [];
108
+ for (const id of callIds) {
109
+ const call = calls.get(id);
110
+ const result = results.get(id);
111
+ if (!call || !result) return void 0;
112
+ runs.push({ ...call, ...result });
113
+ }
114
+ return runs;
115
+ }
116
+ function describe(runs) {
117
+ return runs.map((run) => `${run.name}: ${run.input}
118
+ => ${run.output.slice(0, OUTPUT_LENGTH)}`).join("\n\n");
119
+ }
120
+ async function spend(tx, conversation, answer) {
121
+ const models = (await tx.doc(UsageDoc, conversation)).models;
122
+ const key = `${answer.provider}/${answer.model}`;
123
+ const usage = answer.usage;
124
+ const total = Object.hasOwn(models, key) ? models[key] : void 0;
125
+ if (!total) {
126
+ models[key] = JSON.parse(JSON.stringify(usage));
127
+ return;
128
+ }
129
+ for (const field of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"])
130
+ total[field] += usage[field];
131
+ for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"])
132
+ total.cost[field] += usage.cost[field];
133
+ }
134
+ function modelName(model) {
135
+ return model ? `${model.provider}/${model.modelId}` : "";
136
+ }
137
+ async function attachLabels(options, context = BACKGROUND_CONTEXT) {
138
+ const { harness, agents } = options;
139
+ const now = options.now ?? Date.now;
140
+ const started = now();
141
+ const cap = options.perMinute ?? LABELS_PER_MINUTE;
142
+ const loaded = await harness.commit(
143
+ async (tx) => ({ ...(await tx.doc(LabelsDoc)).labels }),
144
+ context
145
+ );
146
+ const labels = replicatedState(loaded);
147
+ const flying = /* @__PURE__ */ new Map();
148
+ const spent = /* @__PURE__ */ new Map();
149
+ const admit = (agentId) => () => {
150
+ const at = now();
151
+ const recent = (spent.get(agentId) ?? []).filter((time) => at - time < MINUTE);
152
+ spent.set(agentId, recent);
153
+ if (recent.length >= cap) return false;
154
+ recent.push(at);
155
+ return true;
156
+ };
157
+ const keep = (key, label, conversation, answer) => harness.commit(async (tx) => {
158
+ const doc = await tx.doc(LabelsDoc);
159
+ doc.labels[key] = label;
160
+ const keys = Object.keys(doc.labels);
161
+ for (const old of keys.slice(0, Math.max(0, keys.length - LABELS_KEPT)))
162
+ delete doc.labels[old];
163
+ await spend(tx, conversation, answer);
164
+ return { ...doc.labels };
165
+ }, BACKGROUND_CONTEXT);
166
+ const make = async (agentId, callIds, key) => {
167
+ const conversation = await agents.conversation(agentId, BACKGROUND_CONTEXT);
168
+ if (!conversation) return null;
169
+ const runs = clusterOf((await conversation.context(BACKGROUND_CONTEXT)).entries, callIds);
170
+ if (!runs || Math.max(...runs.map((run) => run.at)) < started) return null;
171
+ if (vaultHold(options.vault?.value)) return null;
172
+ const text = await scrubbed(describe(runs));
173
+ const model = modelName((await conversation.agent(BACKGROUND_CONTEXT)).model);
174
+ const asked = await options.label(text, model, BACKGROUND_CONTEXT, admit(agentId));
175
+ if (!asked) return null;
176
+ const label = cleanLabel(asked.text);
177
+ labels.replace(BACKGROUND_CONTEXT, await keep(key, label, conversation.id, asked.answer));
178
+ return label || null;
179
+ };
180
+ return {
181
+ labels,
182
+ label(agentId, callIds, _context) {
183
+ if (!callIds.length) return Promise.resolve(null);
184
+ const key = labelKey(callIds);
185
+ const found = shownLabel(labels.value, callIds);
186
+ if (found !== void 0) return Promise.resolve(found);
187
+ const known = flying.get(key);
188
+ if (known !== void 0) return known;
189
+ const made = make(agentId, callIds, key).catch(() => null).finally(() => flying.delete(key));
190
+ flying.set(key, made);
191
+ return made;
192
+ }
193
+ };
194
+ }
195
+ export {
196
+ LABELS_KEPT,
197
+ LABELS_PER_MINUTE,
198
+ LABEL_WAIT_MS,
199
+ LabelsDoc,
200
+ attachLabels,
201
+ cleanLabel,
202
+ clusterOf,
203
+ describe,
204
+ visibleLabels,
205
+ watchLabel
206
+ };
207
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/titles/labels.ts"],
4
+ "sourcesContent": ["import { type Context, type ReplicatedState, replicatedState } from '@earendil-works/chord';\nimport { BACKGROUND_CONTEXT } from '@earendil-works/chord/context';\nimport type { AssistantMessage, Usage } from '@earendil-works/pi-ai';\nimport {\n type ConversationId,\n type ConversationView,\n defineDoc,\n type EntryRecord,\n type Harness,\n type Tx,\n UsageDoc,\n} from '@earendil-works/pi-durable';\nimport { answerText } from '../agent.ts';\nimport type { Agents } from '../agents.ts';\nimport type { VaultState } from '../secrets/vault.ts';\nimport { vaultHold } from '../secrets/view.ts';\nimport { type Labeler, scrubbed } from './generate.ts';\nimport { labelKey, shownLabel } from './shown.ts';\n\nexport type LabelsState = { labels: Record<string, string> };\n\nexport const LabelsDoc = defineDoc<LabelsState>({\n kind: 'slicc.labels',\n version: 1,\n scope: 'session',\n initial: () => ({ labels: {} }),\n});\n\nexport const LABELS_KEPT = 500;\nexport const LABELS_PER_MINUTE = 6;\nconst MINUTE = 60_000;\nconst LABEL_WORDS = 8;\nconst LABEL_LENGTH = 80;\nconst OUTPUT_LENGTH = 600;\n\nexport const LABEL_WAIT_MS = 30_000;\n\nfunction callIdsOf(views: readonly ConversationView[]): Set<string> {\n const ids = new Set<string>();\n for (const view of views)\n for (const entry of view.entries) {\n const content = (entry.model?.[0] as Said | undefined)?.content;\n if (entry.kind === 'pi.assistant' && Array.isArray(content))\n for (const block of content as Block[])\n if (block.type === 'toolCall' && block.id) ids.add(block.id);\n }\n return ids;\n}\n\nexport function visibleLabels(\n labels: ReplicatedState<Record<string, string>>,\n sources: readonly ReplicatedState<unknown>[],\n views: () => readonly ConversationView[]\n) {\n const pick = () => {\n const ids = callIdsOf(views());\n return Object.fromEntries(\n Object.entries(labels.value as Record<string, string>).filter(([key]) =>\n key.split(' ').every((id) => ids.has(id))\n )\n );\n };\n const state = replicatedState<Record<string, string>>(pick());\n let queued = false;\n const update = () => {\n if (queued) return;\n queued = true;\n queueMicrotask(() => {\n queued = false;\n const next = pick();\n const before = state.value;\n const keys = Object.keys(next);\n if (\n keys.length !== Object.keys(before).length ||\n keys.some((key) => before[key] !== next[key])\n )\n state.replace(BACKGROUND_CONTEXT, next);\n });\n };\n const offs = [labels, ...sources].map((source) => source.subscribe(update));\n return {\n state,\n dispose() {\n for (const off of offs) off();\n },\n };\n}\n\nexport function watchLabel(\n labels: ReplicatedState<Record<string, string>>,\n waitMs = LABEL_WAIT_MS\n) {\n return (_agentId: string, callIds: readonly string[]): Promise<string | null> => {\n const found = shownLabel(labels.value, callIds);\n if (found !== undefined || !callIds.length) return Promise.resolve(found ?? null);\n return new Promise((resolve) => {\n const done = (label: string | null) => {\n clearTimeout(timer);\n off();\n resolve(label);\n };\n const timer = setTimeout(() => done(null), waitMs);\n const off = labels.subscribe(() => {\n const label = shownLabel(labels.value, callIds);\n if (label !== undefined) done(label);\n });\n });\n };\n}\n\nexport function cleanLabel(text: string): string {\n return text\n .replace(/\\s+/g, ' ')\n .trim()\n .replace(/^[\"'\u201C\u201D]+|[\"'\u201C\u201D]+$/g, '')\n .replace(/\\.+$/, '')\n .trim()\n .split(' ')\n .slice(0, LABEL_WORDS)\n .join(' ')\n .slice(0, LABEL_LENGTH);\n}\n\nexport type Run = { name: string; input: string; output: string; at: number };\n\ntype Block = { type?: string; id?: string; name?: string; arguments?: unknown };\n\ntype Said = { content?: unknown; toolCallId?: string; timestamp?: number };\n\nexport function clusterOf(\n entries: readonly EntryRecord[],\n callIds: readonly string[]\n): Run[] | undefined {\n const calls = new Map<string, Pick<Run, 'name' | 'input'>>();\n const results = new Map<string, Pick<Run, 'output' | 'at'>>();\n for (const entry of entries) {\n const said = entry.model?.[0] as Said | undefined;\n if (entry.kind === 'pi.assistant' && Array.isArray(said?.content))\n for (const block of said.content as Block[])\n if (block.type === 'toolCall' && block.id)\n calls.set(block.id, {\n name: block.name ?? 'tool',\n input: JSON.stringify(block.arguments ?? {}),\n });\n if (entry.kind === 'pi.tool-result' && said?.toolCallId)\n results.set(said.toolCallId, {\n output: answerText(said.content),\n at: said.timestamp ?? 0,\n });\n }\n const runs: Run[] = [];\n for (const id of callIds) {\n const call = calls.get(id);\n const result = results.get(id);\n if (!call || !result) return undefined;\n runs.push({ ...call, ...result });\n }\n return runs;\n}\n\nexport function describe(runs: readonly Run[]): string {\n return runs\n .map((run) => `${run.name}: ${run.input}\\n=> ${run.output.slice(0, OUTPUT_LENGTH)}`)\n .join('\\n\\n');\n}\n\ntype Counted = Record<'input' | 'output' | 'cacheRead' | 'cacheWrite' | 'totalTokens', number> & {\n cost: Record<'input' | 'output' | 'cacheRead' | 'cacheWrite' | 'total', number>;\n};\n\nasync function spend(tx: Tx, conversation: ConversationId, answer: AssistantMessage) {\n const models = (await tx.doc(UsageDoc, conversation)).models as unknown as Record<\n string,\n Counted\n >;\n const key = `${answer.provider}/${answer.model}`;\n const usage = answer.usage as Usage;\n const total = Object.hasOwn(models, key) ? models[key] : undefined;\n if (!total) {\n models[key] = JSON.parse(JSON.stringify(usage)) as Counted;\n return;\n }\n for (const field of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'] as const)\n total[field] += usage[field];\n for (const field of ['input', 'output', 'cacheRead', 'cacheWrite', 'total'] as const)\n total.cost[field] += usage.cost[field];\n}\n\nexport interface LabelsAttach {\n harness: Pick<Harness, 'commit'>;\n agents: Pick<Agents, 'conversation'>;\n label: Labeler;\n vault?: Pick<ReplicatedState<VaultState>, 'value'>;\n now?: () => number;\n perMinute?: number;\n}\n\nexport interface LabelsRuntime {\n labels: ReplicatedState<Record<string, string>>;\n label(agentId: string, callIds: readonly string[], context: Context): Promise<string | null>;\n}\n\nfunction modelName(model: { provider: string; modelId: string } | undefined): string {\n return model ? `${model.provider}/${model.modelId}` : '';\n}\n\nexport async function attachLabels(\n options: LabelsAttach,\n context: Context = BACKGROUND_CONTEXT\n): Promise<LabelsRuntime> {\n const { harness, agents } = options;\n const now = options.now ?? Date.now;\n const started = now();\n const cap = options.perMinute ?? LABELS_PER_MINUTE;\n const loaded = await harness.commit(\n async (tx) => ({ ...(await tx.doc(LabelsDoc)).labels }),\n context\n );\n const labels = replicatedState<Record<string, string>>(loaded);\n const flying = new Map<string, Promise<string | null>>();\n const spent = new Map<string, number[]>();\n const admit = (agentId: string) => () => {\n const at = now();\n const recent = (spent.get(agentId) ?? []).filter((time) => at - time < MINUTE);\n spent.set(agentId, recent);\n if (recent.length >= cap) return false;\n recent.push(at);\n return true;\n };\n const keep = (\n key: string,\n label: string,\n conversation: ConversationId,\n answer: AssistantMessage\n ) =>\n harness.commit(async (tx) => {\n const doc = await tx.doc(LabelsDoc);\n doc.labels[key] = label;\n const keys = Object.keys(doc.labels);\n for (const old of keys.slice(0, Math.max(0, keys.length - LABELS_KEPT)))\n delete doc.labels[old];\n await spend(tx, conversation, answer);\n return { ...doc.labels };\n }, BACKGROUND_CONTEXT);\n const make = async (agentId: string, callIds: readonly string[], key: string) => {\n const conversation = await agents.conversation(agentId, BACKGROUND_CONTEXT);\n if (!conversation) return null;\n const runs = clusterOf((await conversation.context(BACKGROUND_CONTEXT)).entries, callIds);\n if (!runs || Math.max(...runs.map((run) => run.at)) < started) return null;\n if (vaultHold(options.vault?.value)) return null;\n const text = await scrubbed(describe(runs));\n const model = modelName((await conversation.agent(BACKGROUND_CONTEXT)).model);\n const asked = await options.label(text, model, BACKGROUND_CONTEXT, admit(agentId));\n if (!asked) return null;\n const label = cleanLabel(asked.text);\n labels.replace(BACKGROUND_CONTEXT, await keep(key, label, conversation.id, asked.answer));\n return label || null;\n };\n return {\n labels,\n label(agentId, callIds, _context) {\n if (!callIds.length) return Promise.resolve(null);\n const key = labelKey(callIds);\n const found = shownLabel(labels.value, callIds);\n if (found !== undefined) return Promise.resolve(found);\n const known = flying.get(key);\n if (known !== undefined) return known;\n const made = make(agentId, callIds, key)\n .catch(() => null)\n .finally(() => flying.delete(key));\n flying.set(key, made);\n return made;\n },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ function labelKey(callIds) {
2
+ return callIds.join(" ");
3
+ }
4
+ function shownLabel(labels, callIds) {
5
+ const key = labelKey(callIds);
6
+ return labels && Object.hasOwn(labels, key) ? labels[key] || null : void 0;
7
+ }
8
+ export {
9
+ labelKey,
10
+ shownLabel
11
+ };
12
+ //# sourceMappingURL=shown.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/titles/shown.ts"],
4
+ "sourcesContent": ["export function labelKey(callIds: readonly string[]): string {\n return callIds.join(' ');\n}\n\nexport function shownLabel(\n labels: Readonly<Record<string, string>> | undefined,\n callIds: readonly string[]\n): string | null | undefined {\n const key = labelKey(callIds);\n return labels && Object.hasOwn(labels, key) ? labels[key] || null : undefined;\n}\n"],
5
+ "mappings": "AAAO,SAAS,SAAS,SAAoC;AAC3D,SAAO,QAAQ,KAAK,GAAG;AACzB;AAEO,SAAS,WACd,QACA,SAC2B;AAC3B,QAAM,MAAM,SAAS,OAAO;AAC5B,SAAO,UAAU,OAAO,OAAO,QAAQ,GAAG,IAAI,OAAO,GAAG,KAAK,OAAO;AACtE;",
6
+ "names": []
7
+ }
@@ -25,6 +25,7 @@ export interface AgentConnection {
25
25
  readonly changes?: ReplicatedState<ChangesView>;
26
26
  readonly monitor?: ReplicatedState<MonitorFeed>;
27
27
  readonly vault?: ReplicatedState<VaultState>;
28
+ readonly labels?: ReplicatedState<Record<string, string>>;
28
29
  readonly settings: AgentSettings;
29
30
  readonly remote?: AgentRemote;
30
31
  readonly pages?: AgentPages;
@@ -11,6 +11,7 @@ import type { SecretName } from './secrets/names.ts';
11
11
  import type { AgentControl } from './services.ts';
12
12
  import type { SkillsRuntime } from './skills/index.ts';
13
13
  import type { SprinklesRuntime } from './sprinkles/index.ts';
14
+ import type { LabelsRuntime } from './titles/labels.ts';
14
15
  import { type Uploads } from './uploads.ts';
15
16
  export interface HostLicks {
16
17
  licks: Licks;
@@ -22,4 +23,4 @@ export interface HostScoops {
22
23
  }
23
24
  export declare function readable(out: string): string;
24
25
  export declare function submissionId(value: string): SubmissionId | undefined;
25
- export declare function createAgentControl(harness: Harness, target: Conversation | Cones, deliveries?: Deliveries, licks?: HostLicks, scoops?: HostScoops, skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>, sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>, memory?: Pick<MemoryRuntime, 'save' | 'remove'>, freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>, changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>, monitor?: Pick<Monitor, 'open' | 'close'>, secrets?: () => readonly SecretName[], uploads?: Uploads): AgentControl;
26
+ export declare function createAgentControl(harness: Harness, target: Conversation | Cones, deliveries?: Deliveries, licks?: HostLicks, scoops?: HostScoops, skills?: Pick<SkillsRuntime, 'expand' | 'commandsFor'>, sprinkles?: Pick<SprinklesRuntime, 'send' | 'call'>, memory?: Pick<MemoryRuntime, 'save' | 'remove'>, freezer?: Pick<FreezerRuntime, 'freeze' | 'newChat' | 'thaw' | 'discard'>, changes?: Pick<ChangesRuntime, 'open' | 'accept' | 'revert'>, monitor?: Pick<Monitor, 'open' | 'close'>, secrets?: () => readonly SecretName[], uploads?: Uploads, labels?: Pick<LabelsRuntime, 'label'>): AgentControl;
@@ -14,6 +14,7 @@ import { AgentSettings } from './services.ts';
14
14
  import type { SkillsRuntime } from './skills/index.ts';
15
15
  import type { SprinklesRuntime } from './sprinkles/index.ts';
16
16
  import { type Thumbnail } from './thumbs.ts';
17
+ import { type LabelsRuntime } from './titles/labels.ts';
17
18
  import type { Uploads } from './uploads.ts';
18
19
  import type { PortEndpoint } from './wire.ts';
19
20
  export interface ConnectOptions {
@@ -51,4 +52,5 @@ export declare function hostAgent(agent: Agent, options?: {
51
52
  secrets?: HostSecrets;
52
53
  uploads?: Uploads;
53
54
  thumbnail?: Thumbnail;
55
+ labels?: LabelsRuntime;
54
56
  }): Promise<AgentHost>;
@@ -87,6 +87,7 @@ export interface AgentControl {
87
87
  monitorClose(lease: string, context: Context): Promise<void>;
88
88
  image(ref: ImageRef, context: Context): Promise<StoredImage>;
89
89
  agentCommands(agentId: string, context: Context): Promise<Command[]>;
90
+ toolLabel(agentId: string, callIds: string[], context: Context): Promise<string | null>;
90
91
  }
91
92
  export interface ImageRef {
92
93
  entry: number;
@@ -247,6 +248,7 @@ export interface AgentTranscript {
247
248
  readonly changes: ReplicatedState<ChangesView>;
248
249
  readonly monitor: ReplicatedState<MonitorFeed>;
249
250
  readonly vault: ReplicatedState<VaultState>;
251
+ readonly labels: ReplicatedState<Record<string, string>>;
250
252
  }
251
253
  export interface PrivateRevision {
252
254
  revision: number;
@@ -38,6 +38,7 @@ export declare class AgentAdapter extends Emitter<AgentAdapterEvents> implements
38
38
  createScoop(parentId: string, name: string): Agent;
39
39
  drop(agentId: string): Promise<void>;
40
40
  createCone(name: string): Promise<Agent>;
41
+ toolLabel(agentId: string, callIds: readonly string[]): Promise<string | null>;
41
42
  frozen(): readonly FrozenCone[];
42
43
  thaw(id: string): Agent | null;
43
44
  discard(id: string): void;
@@ -1,11 +1,23 @@
1
1
  import type { Context } from '@earendil-works/chord';
2
+ import type { AssistantMessage } from '@earendil-works/pi-ai';
2
3
  import type { Models } from '@earendil-works/pi-ai/models';
3
4
  import type { EntryRecord } from '@earendil-works/pi-durable';
4
5
  import type { ExecutionEnv } from '@earendil-works/pi-durable/env';
5
6
  export declare const TITLE_PROMPT = "Give this chat a title of three to six words that says what it is about, in the language the user writes in. Answer with the title only, without quotes.";
7
+ export declare const LABEL_PROMPT = "Label this group of tool runs in three to eight words, as an imperative that says what they do, such as \"Check the failing build logs\". Answer with the label only, without quotes.";
6
8
  export declare const TRANSCRIPT_LENGTH = 4000;
7
9
  export declare function titlesOn(text: string | undefined): boolean;
10
+ export declare function labelsOn(text: string | undefined): boolean;
8
11
  export declare function settingsText(env: Pick<ExecutionEnv, 'readTextFile'>, home: string, dir?: string): (context: Context) => Promise<string | undefined>;
9
12
  export declare function transcriptOf(entries: readonly EntryRecord[]): string;
13
+ export declare function scrubbed(raw: string): Promise<string>;
10
14
  export declare function scrubTranscript(entries: readonly EntryRecord[]): Promise<string>;
11
- export declare function titler(models: Models, settings: (context: Context) => Promise<string | undefined>): (transcript: string, model: string, context: Context) => Promise<string | undefined>;
15
+ type Settings = (context: Context) => Promise<string | undefined>;
16
+ export type Asked = {
17
+ text: string;
18
+ answer: AssistantMessage;
19
+ };
20
+ export declare function titler(models: Models, settings: Settings): (transcript: string, model: string, context: Context) => Promise<string | undefined>;
21
+ export declare function labeler(models: Models, settings: Settings): (transcript: string, model: string, context: Context, admit?: () => boolean) => Promise<Asked | undefined>;
22
+ export type Labeler = ReturnType<typeof labeler>;
23
+ export {};
@@ -0,0 +1,39 @@
1
+ import { type Context, type ReplicatedState } from '@earendil-works/chord';
2
+ import { type ConversationView, type EntryRecord, type Harness } from '@earendil-works/pi-durable';
3
+ import type { Agents } from '../agents.ts';
4
+ import type { VaultState } from '../secrets/vault.ts';
5
+ import { type Labeler } from './generate.ts';
6
+ export type LabelsState = {
7
+ labels: Record<string, string>;
8
+ };
9
+ export declare const LabelsDoc: import("@earendil-works/pi-durable").SessionDocToken<LabelsState>;
10
+ export declare const LABELS_KEPT = 500;
11
+ export declare const LABELS_PER_MINUTE = 6;
12
+ export declare const LABEL_WAIT_MS = 30000;
13
+ export declare function visibleLabels(labels: ReplicatedState<Record<string, string>>, sources: readonly ReplicatedState<unknown>[], views: () => readonly ConversationView[]): {
14
+ state: import("@earendil-works/chord").MutableReplicatedState<Record<string, string>>;
15
+ dispose(): void;
16
+ };
17
+ export declare function watchLabel(labels: ReplicatedState<Record<string, string>>, waitMs?: number): (_agentId: string, callIds: readonly string[]) => Promise<string | null>;
18
+ export declare function cleanLabel(text: string): string;
19
+ export type Run = {
20
+ name: string;
21
+ input: string;
22
+ output: string;
23
+ at: number;
24
+ };
25
+ export declare function clusterOf(entries: readonly EntryRecord[], callIds: readonly string[]): Run[] | undefined;
26
+ export declare function describe(runs: readonly Run[]): string;
27
+ export interface LabelsAttach {
28
+ harness: Pick<Harness, 'commit'>;
29
+ agents: Pick<Agents, 'conversation'>;
30
+ label: Labeler;
31
+ vault?: Pick<ReplicatedState<VaultState>, 'value'>;
32
+ now?: () => number;
33
+ perMinute?: number;
34
+ }
35
+ export interface LabelsRuntime {
36
+ labels: ReplicatedState<Record<string, string>>;
37
+ label(agentId: string, callIds: readonly string[], context: Context): Promise<string | null>;
38
+ }
39
+ export declare function attachLabels(options: LabelsAttach, context?: Context): Promise<LabelsRuntime>;
@@ -0,0 +1,2 @@
1
+ export declare function labelKey(callIds: readonly string[]): string;
2
+ export declare function shownLabel(labels: Readonly<Record<string, string>> | undefined, callIds: readonly string[]): string | null | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-agent",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "SLICC's agent core on pi durable: an agent worker, session storage, remoting and pi extensions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -63,7 +63,7 @@
63
63
  "devDependencies": {
64
64
  "@ai-ecoverse/slicc-kernel": "1.47.1",
65
65
  "@ai-ecoverse/slicc-scrub": "1.0.0",
66
- "@ai-ecoverse/slicc-shared-web": "1.13.2",
66
+ "@ai-ecoverse/slicc-shared-web": "1.13.3",
67
67
  "@ai-ecoverse/slicc-spectrum": "1.69.2",
68
68
  "@ai-ecoverse/wasm-bash": "5.3.0-18",
69
69
  "@ai-ecoverse/wasm-coreutils": "9.12.0-5",