@things-factory/ai-assistant 10.1.202 → 10.1.205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/assistant-mode/index.d.ts +1 -1
- package/dist-server/service/assistant-mode/index.js.map +1 -1
- package/dist-server/service/assistant-mode/mode-categories.d.ts +10 -0
- package/dist-server/service/assistant-mode/mode-categories.js +10 -0
- package/dist-server/service/assistant-mode/mode-categories.js.map +1 -1
- package/dist-server/service/assistant-mode/types.d.ts +14 -0
- package/dist-server/service/assistant-mode/types.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -1
- package/dist-server/service/index.js +4 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/unattended-run/assistant-run.d.ts +45 -0
- package/dist-server/service/unattended-run/assistant-run.js +248 -0
- package/dist-server/service/unattended-run/assistant-run.js.map +1 -0
- package/dist-server/service/unattended-run/index.d.ts +8 -0
- package/dist-server/service/unattended-run/index.js +15 -0
- package/dist-server/service/unattended-run/index.js.map +1 -0
- package/dist-server/service/unattended-run/limits.d.ts +9 -0
- package/dist-server/service/unattended-run/limits.js +24 -0
- package/dist-server/service/unattended-run/limits.js.map +1 -0
- package/dist-server/service/unattended-run/run-as.d.ts +24 -0
- package/dist-server/service/unattended-run/run-as.js +26 -0
- package/dist-server/service/unattended-run/run-as.js.map +1 -0
- package/dist-server/service/unattended-run/run-budget.d.ts +24 -0
- package/dist-server/service/unattended-run/run-budget.js +46 -0
- package/dist-server/service/unattended-run/run-budget.js.map +1 -0
- package/dist-server/service/unattended-run/run-ledger.d.ts +13 -0
- package/dist-server/service/unattended-run/run-ledger.js +29 -0
- package/dist-server/service/unattended-run/run-ledger.js.map +1 -0
- package/dist-server/service/unattended-run/run-outcome.d.ts +9 -0
- package/dist-server/service/unattended-run/run-outcome.js +46 -0
- package/dist-server/service/unattended-run/run-outcome.js.map +1 -0
- package/dist-server/service/unattended-run/run-store.d.ts +31 -0
- package/dist-server/service/unattended-run/run-store.js +108 -0
- package/dist-server/service/unattended-run/run-store.js.map +1 -0
- package/dist-server/service/unattended-run/run-unattended.d.ts +50 -0
- package/dist-server/service/unattended-run/run-unattended.js +214 -0
- package/dist-server/service/unattended-run/run-unattended.js.map +1 -0
- package/dist-server/service/unattended-run/unattended-prompt.d.ts +3 -0
- package/dist-server/service/unattended-run/unattended-prompt.js +27 -0
- package/dist-server/service/unattended-run/unattended-prompt.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/server/service/assistant-mode/index.ts +1 -1
- package/server/service/assistant-mode/mode-categories.ts +10 -0
- package/server/service/assistant-mode/types.ts +15 -0
- package/server/service/index.ts +4 -1
- package/server/service/unattended-run/assistant-run.ts +199 -0
- package/server/service/unattended-run/index.ts +10 -0
- package/server/service/unattended-run/limits.ts +24 -0
- package/server/service/unattended-run/run-as.ts +38 -0
- package/server/service/unattended-run/run-budget.ts +58 -0
- package/server/service/unattended-run/run-ledger.ts +31 -0
- package/server/service/unattended-run/run-outcome.ts +43 -0
- package/server/service/unattended-run/run-store-race.pg.test.ts +83 -0
- package/server/service/unattended-run/run-store.ts +162 -0
- package/server/service/unattended-run/run-unattended.test.ts +477 -0
- package/server/service/unattended-run/run-unattended.ts +246 -0
- package/server/service/unattended-run/unattended-prompt.ts +24 -0
- package/translations/en.json +15 -1
- package/translations/es.json +15 -1
- package/translations/ja.json +15 -1
- package/translations/ko.json +15 -1
- package/translations/ms.json +15 -1
- package/translations/zh.json +15 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAssistantUnattended = runAssistantUnattended;
|
|
4
|
+
exports.findAssistantRun = findAssistantRun;
|
|
5
|
+
/*
|
|
6
|
+
* Run an assistant mode with no person in it, for scheduled work, and keep what it wrote as a draft
|
|
7
|
+
* (operato-application/design/ai/unattended-run.md, ADR-0142 §8.1, user ruling C 2026-10-20).
|
|
8
|
+
*
|
|
9
|
+
* A server function and no GraphQL door: a door would let a person name someone else as `runAs`. The caller is a
|
|
10
|
+
* server's scheduled task - Office's periodic strategy review is the first.
|
|
11
|
+
*
|
|
12
|
+
* identity (domain, source, subject, scheduledAt) is one row - a retry hands the stored run back (§5)
|
|
13
|
+
* who the run-as user, judged again now; refused when they cannot ask the assistant themselves (§2)
|
|
14
|
+
* tools the mode's read tools behind that user's own doors; no 입력안, no cards kept (§3)
|
|
15
|
+
* limits the mode's: model requests, tokens, wall time (§4)
|
|
16
|
+
* outcome succeeded with a draft, failed or refused with a code - never an empty success (§7)
|
|
17
|
+
*
|
|
18
|
+
* Every tool call goes through the registry door, so the recorders write it down as they do a turn's (§6).
|
|
19
|
+
*/
|
|
20
|
+
const ai_client_base_1 = require("@things-factory/ai-client-base");
|
|
21
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
22
|
+
const shell_1 = require("@things-factory/shell");
|
|
23
|
+
const registry_js_1 = require("../assistant-mode/registry.js");
|
|
24
|
+
const mode_categories_js_1 = require("../assistant-mode/mode-categories.js");
|
|
25
|
+
const message_doors_js_1 = require("../chat-message/message-doors.js");
|
|
26
|
+
const grounding_sources_js_1 = require("../grounding-sources.js");
|
|
27
|
+
const tool_access_js_1 = require("../tool-access.js");
|
|
28
|
+
const assistant_run_js_1 = require("./assistant-run.js");
|
|
29
|
+
const limits_js_1 = require("./limits.js");
|
|
30
|
+
const run_budget_js_1 = require("./run-budget.js");
|
|
31
|
+
const run_as_js_1 = require("./run-as.js");
|
|
32
|
+
const run_store_js_1 = require("./run-store.js");
|
|
33
|
+
const unattended_prompt_js_1 = require("./unattended-prompt.js");
|
|
34
|
+
const KEY = /^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*$/;
|
|
35
|
+
function invalid(field) {
|
|
36
|
+
const error = new Error(`runAssistantUnattended: '${field}' is missing or not usable`);
|
|
37
|
+
error.code = 'assistant-run-input-invalid';
|
|
38
|
+
error.params = { field };
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
function checkInput(input) {
|
|
42
|
+
if (!input?.domain?.id)
|
|
43
|
+
throw invalid('domain');
|
|
44
|
+
if (!input.runAs)
|
|
45
|
+
throw invalid('runAs');
|
|
46
|
+
if (!KEY.test(String(input.run?.source ?? '')) || input.run.source.length > 64)
|
|
47
|
+
throw invalid('run.source');
|
|
48
|
+
if (!String(input.run.subject ?? '').trim() || String(input.run.subject).length > 64)
|
|
49
|
+
throw invalid('run.subject');
|
|
50
|
+
if (!(input.run.scheduledAt instanceof Date) || !Number.isFinite(input.run.scheduledAt.valueOf()))
|
|
51
|
+
throw invalid('run.scheduledAt');
|
|
52
|
+
if (!input.mode?.kind)
|
|
53
|
+
throw invalid('mode.kind');
|
|
54
|
+
if (!String(input.instruction ?? '').trim())
|
|
55
|
+
throw invalid('instruction');
|
|
56
|
+
}
|
|
57
|
+
function defaultDeps() {
|
|
58
|
+
return {
|
|
59
|
+
store: (0, run_store_js_1.typeormRunStore)(() => (0, shell_1.getRepository)(assistant_run_js_1.AssistantRun)),
|
|
60
|
+
runAs: {
|
|
61
|
+
findUser: id => (0, shell_1.getRepository)(auth_base_1.User).findOne({ where: { id } }),
|
|
62
|
+
mayCheckin: (user, domain) => (0, auth_base_1.canUserCheckinDomain)(user, domain),
|
|
63
|
+
/* No request: no address to refuse from, and no license prohibition - those cover writes this run never makes. */
|
|
64
|
+
permits: (gate, user, domain) => (0, auth_base_1.checkPermission)(gate, user, domain, false, [])
|
|
65
|
+
},
|
|
66
|
+
clientFor: ai_client_base_1.aiClientFor,
|
|
67
|
+
toolAccess: state => (0, tool_access_js_1.toolAccessFor)({ state }),
|
|
68
|
+
now: () => new Date()
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Why the loop stopped, as the run's code. Only a clean end with text is a draft. */
|
|
72
|
+
function failureOf(loop, baseText) {
|
|
73
|
+
const abort = loop?.abortReason;
|
|
74
|
+
if (abort?.type === 'max_iterations')
|
|
75
|
+
return { code: 'assistant-run-step-limit', params: { steps: abort.iter } };
|
|
76
|
+
if (abort?.type === 'provider_error')
|
|
77
|
+
return { code: 'assistant-run-model-error', params: { message: String(abort.message ?? '').slice(0, 500) } };
|
|
78
|
+
if (abort)
|
|
79
|
+
return { code: 'assistant-run-model-error', params: { stopped: abort.type } };
|
|
80
|
+
if (!baseText.trim())
|
|
81
|
+
return { code: 'assistant-run-empty-draft' };
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const TIMED_OUT = Symbol('timed-out');
|
|
85
|
+
async function runAssistantUnattended(input, deps = defaultDeps()) {
|
|
86
|
+
checkInput(input);
|
|
87
|
+
const { domain, run } = input;
|
|
88
|
+
const mode = (0, registry_js_1.getAssistantMode)(input.mode.kind);
|
|
89
|
+
const limits = (0, limits_js_1.unattendedLimits)(mode?.unattended);
|
|
90
|
+
const startedAt = deps.now();
|
|
91
|
+
/* The account as stored now - read once, and judged after the occurrence is held. */
|
|
92
|
+
const account = await deps.runAs.findUser(input.runAs);
|
|
93
|
+
const { claimed, row } = await deps.store.claim({ domainId: domain.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt }, {
|
|
94
|
+
name: `${run.source} ${run.subject} ${run.scheduledAt.toISOString()}`,
|
|
95
|
+
modeKind: input.mode.kind,
|
|
96
|
+
modeTarget: input.mode.target,
|
|
97
|
+
runAsId: account?.id,
|
|
98
|
+
startedAt,
|
|
99
|
+
deadlineAt: new Date(startedAt.valueOf() + limits.timeoutMs + limits_js_1.ABANDON_MARGIN_MS)
|
|
100
|
+
}, startedAt);
|
|
101
|
+
/* Succeeded, or running elsewhere and still alive - the caller gets that row and the model is not called again. */
|
|
102
|
+
if (!claimed)
|
|
103
|
+
return row;
|
|
104
|
+
const held = { id: row.id, attempt: row.attempt };
|
|
105
|
+
const end = (ending) => deps.store.finish(held, { finishedAt: deps.now(), ...ending });
|
|
106
|
+
const refuse = (code, params) => end({ status: 'refused', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null });
|
|
107
|
+
const fail = (code, params, spent) => end({ status: 'failed', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null, ...spent });
|
|
108
|
+
if (!mode)
|
|
109
|
+
return refuse('unknown-assistant-mode', { kind: input.mode.kind });
|
|
110
|
+
if (!mode.unattended)
|
|
111
|
+
return refuse('assistant-mode-not-unattended', { kind: mode.kind });
|
|
112
|
+
const judged = await (0, run_as_js_1.judgeRunAs)(account, domain, deps.runAs);
|
|
113
|
+
if ('refused' in judged)
|
|
114
|
+
return refuse(judged.refused, { userId: input.runAs });
|
|
115
|
+
const user = judged.user;
|
|
116
|
+
let agentClient;
|
|
117
|
+
try {
|
|
118
|
+
agentClient = await deps.clientFor({ domain: domain, user, modeKind: mode.kind });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
/* No AI here, or not the agent asked for - nothing ran. Its own code, as the conversation door says it. */
|
|
122
|
+
if (error?.code)
|
|
123
|
+
return refuse(String(error.code), error.params);
|
|
124
|
+
return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) });
|
|
125
|
+
}
|
|
126
|
+
const { client, spend } = (0, run_budget_js_1.budgetedClient)(agentClient, limits);
|
|
127
|
+
const reads = [];
|
|
128
|
+
let over = false;
|
|
129
|
+
const state = { domain, user, lng: input.lng ?? user.locale, aiAgentId: agentClient.agent.id };
|
|
130
|
+
const categories = (0, mode_categories_js_1.unattendedToolCategories)(mode);
|
|
131
|
+
const systemPrompt = (0, unattended_prompt_js_1.unattendedPrompt)(mode.systemPrompt, state.lng);
|
|
132
|
+
const messages = [{ role: 'user', content: input.instruction }];
|
|
133
|
+
const abort = new AbortController();
|
|
134
|
+
let timer;
|
|
135
|
+
const timeout = new Promise(resolve => {
|
|
136
|
+
timer = setTimeout(() => {
|
|
137
|
+
abort.abort();
|
|
138
|
+
resolve(TIMED_OUT);
|
|
139
|
+
}, limits.timeoutMs);
|
|
140
|
+
});
|
|
141
|
+
const spent = () => ({
|
|
142
|
+
reads: JSON.stringify(reads),
|
|
143
|
+
agentId: agentClient.agent.id,
|
|
144
|
+
clientId: agentClient.id,
|
|
145
|
+
modelCalls: spend.modelCalls,
|
|
146
|
+
inputTokens: spend.inputTokens,
|
|
147
|
+
outputTokens: spend.outputTokens
|
|
148
|
+
});
|
|
149
|
+
let result;
|
|
150
|
+
try {
|
|
151
|
+
result = await Promise.race([
|
|
152
|
+
(0, ai_client_base_1.registryAssistantChat)({
|
|
153
|
+
client,
|
|
154
|
+
messages,
|
|
155
|
+
systemPrompt,
|
|
156
|
+
toolCategories: categories,
|
|
157
|
+
mode: { kind: mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },
|
|
158
|
+
toolAccess: deps.toolAccess(state),
|
|
159
|
+
hostContext: input.hostContext,
|
|
160
|
+
state,
|
|
161
|
+
temperature: mode.temperature,
|
|
162
|
+
maxTokens: mode.maxTokens,
|
|
163
|
+
requireToolOnFirstTurn: mode.requireToolOnFirstTurn,
|
|
164
|
+
/* Each step is one request. A run that reaches the last step without an answer failed - no wrap-up call (§4). */
|
|
165
|
+
maxIterations: limits.maxModelCalls,
|
|
166
|
+
salvageOnMaxIterations: false,
|
|
167
|
+
signal: abort.signal,
|
|
168
|
+
onToolCall: record => {
|
|
169
|
+
if (!over)
|
|
170
|
+
reads.push(record);
|
|
171
|
+
},
|
|
172
|
+
run: { id: held.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt.toISOString() },
|
|
173
|
+
groundingSources: (0, grounding_sources_js_1.groundingSourcesFor)({ systemPrompt, messages: messages, hostContext: input.hostContext })
|
|
174
|
+
}),
|
|
175
|
+
timeout
|
|
176
|
+
]);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
over = true;
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
if (error instanceof run_budget_js_1.RunBudgetExceeded)
|
|
182
|
+
return fail(error.code, error.params, spent());
|
|
183
|
+
return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) }, spent());
|
|
184
|
+
}
|
|
185
|
+
over = true;
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
if (result === TIMED_OUT)
|
|
188
|
+
return fail('assistant-run-timeout', { timeoutMs: limits.timeoutMs }, spent());
|
|
189
|
+
/* The loop turns a refused request into a provider error; the budget says which limit it was. */
|
|
190
|
+
if (spend.exceeded)
|
|
191
|
+
return fail('assistant-run-budget-exceeded', spend.exceeded, spent());
|
|
192
|
+
const failure = failureOf(result.loop, result.baseText ?? '');
|
|
193
|
+
if (failure)
|
|
194
|
+
return fail(failure.code, failure.params, spent());
|
|
195
|
+
const doors = (0, message_doors_js_1.doorsForAnswer)((result.loop.toolUsages ?? []).map((usage) => usage?.name).filter(Boolean), name => (0, ai_client_base_1.findToolSpec)(name, categories)?.doors);
|
|
196
|
+
return end({
|
|
197
|
+
status: 'succeeded',
|
|
198
|
+
draft: result.baseText,
|
|
199
|
+
doors,
|
|
200
|
+
cardsDropped: result.loop.proposals?.length ?? 0,
|
|
201
|
+
groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings).slice(0, 1024) : null,
|
|
202
|
+
...spent()
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/** The stored run of an occurrence, for the application that scheduled it - read behind that application's own door. */
|
|
206
|
+
async function findAssistantRun(identity) {
|
|
207
|
+
const repository = (0, shell_1.getRepository)(assistant_run_js_1.AssistantRun);
|
|
208
|
+
if ('id' in identity)
|
|
209
|
+
return repository.findOne({ where: { id: identity.id } });
|
|
210
|
+
return repository.findOne({
|
|
211
|
+
where: { domain: { id: identity.domainId }, source: identity.source, subject: identity.subject, scheduledAt: identity.scheduledAt }
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=run-unattended.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-unattended.js","sourceRoot":"","sources":["../../../server/service/unattended-run/run-unattended.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Run an assistant mode with no person in it, for scheduled work, and keep what it wrote as a draft\n * (operato-application/design/ai/unattended-run.md, ADR-0142 §8.1, user ruling C 2026-10-20).\n *\n * A server function and no GraphQL door: a door would let a person name someone else as `runAs`. The caller is a\n * server's scheduled task - Office's periodic strategy review is the first.\n *\n * identity (domain, source, subject, scheduledAt) is one row - a retry hands the stored run back (§5)\n * who the run-as user, judged again now; refused when they cannot ask the assistant themselves (§2)\n * tools the mode's read tools behind that user's own doors; no 입력안, no cards kept (§3)\n * limits the mode's: model requests, tokens, wall time (§4)\n * outcome succeeded with a draft, failed or refused with a code - never an empty success (§7)\n *\n * Every tool call goes through the registry door, so the recorders write it down as they do a turn's (§6).\n */\nimport { aiClientFor, findToolSpec, registryAssistantChat, type AgentClient, type AiClientRequest, type ToolAccessCheck, type ToolCallRecord } from '@things-factory/ai-client-base'\nimport { User, canUserCheckinDomain, checkPermission } from '@things-factory/auth-base'\nimport { getRepository } from '@things-factory/shell'\n\nimport { getAssistantMode } from '../assistant-mode/registry.js'\nimport { unattendedToolCategories } from '../assistant-mode/mode-categories.js'\nimport { doorsForAnswer } from '../chat-message/message-doors.js'\nimport { groundingSourcesFor } from '../grounding-sources.js'\nimport { toolAccessFor } from '../tool-access.js'\nimport { AssistantRun } from './assistant-run.js'\nimport { ABANDON_MARGIN_MS, unattendedLimits } from './limits.js'\nimport { budgetedClient, RunBudgetExceeded } from './run-budget.js'\nimport { judgeRunAs, type RunAsChecks } from './run-as.js'\nimport { typeormRunStore, type RunEnding, type RunStore } from './run-store.js'\nimport { unattendedPrompt } from './unattended-prompt.js'\n\nexport interface UnattendedRunInput {\n /** The domain the run is in, as loaded - its parent and owner decide who can enter it. */\n domain: { id: string; [key: string]: any }\n /** The user the run acts as - the policy owner. Their privileges and doors only. */\n runAs: string\n /** Which occurrence this is. The same three again is the same run. */\n run: {\n /** Who scheduled it - `<app>.<work>`, e.g. `office.strategy-review`. */\n source: string\n /** What it is about in the source's terms - a policy id. */\n subject: string\n scheduledAt: Date\n }\n /** A registered mode that declares `unattended`. */\n mode: { kind: string; target?: string }\n /** What this occurrence is asked to do - the caller server's text, read by the model (English). */\n instruction: string\n /** What the run is about, for the tools. Filled by the caller's server; still never used for authorization. */\n hostContext?: Record<string, any>\n /** The language of the draft - the owner's. Absent: their account's locale. */\n lng?: string\n}\n\n/** What the run reaches outside itself. The defaults are the server's; the tests hand their own. */\nexport interface UnattendedRunDeps {\n store: RunStore\n runAs: RunAsChecks<any, any>\n clientFor: (request: AiClientRequest) => Promise<AgentClient>\n toolAccess: (state: any) => ToolAccessCheck\n now: () => Date\n}\n\nconst KEY = /^[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*$/\n\nfunction invalid(field: string): Error {\n const error: any = new Error(`runAssistantUnattended: '${field}' is missing or not usable`)\n error.code = 'assistant-run-input-invalid'\n error.params = { field }\n return error\n}\n\nfunction checkInput(input: UnattendedRunInput) {\n if (!input?.domain?.id) throw invalid('domain')\n if (!input.runAs) throw invalid('runAs')\n if (!KEY.test(String(input.run?.source ?? '')) || input.run.source.length > 64) throw invalid('run.source')\n if (!String(input.run.subject ?? '').trim() || String(input.run.subject).length > 64) throw invalid('run.subject')\n if (!(input.run.scheduledAt instanceof Date) || !Number.isFinite(input.run.scheduledAt.valueOf())) throw invalid('run.scheduledAt')\n if (!input.mode?.kind) throw invalid('mode.kind')\n if (!String(input.instruction ?? '').trim()) throw invalid('instruction')\n}\n\nfunction defaultDeps(): UnattendedRunDeps {\n return {\n store: typeormRunStore(() => getRepository(AssistantRun)),\n runAs: {\n findUser: id => getRepository(User).findOne({ where: { id } }),\n mayCheckin: (user, domain) => canUserCheckinDomain(user, domain),\n /* No request: no address to refuse from, and no license prohibition - those cover writes this run never makes. */\n permits: (gate, user, domain) => checkPermission(gate as any, user, domain, false, [])\n },\n clientFor: aiClientFor,\n toolAccess: state => toolAccessFor({ state }),\n now: () => new Date()\n }\n}\n\n/** Why the loop stopped, as the run's code. Only a clean end with text is a draft. */\nfunction failureOf(loop: any, baseText: string): { code: string; params?: any } | undefined {\n const abort = loop?.abortReason\n if (abort?.type === 'max_iterations') return { code: 'assistant-run-step-limit', params: { steps: abort.iter } }\n if (abort?.type === 'provider_error') return { code: 'assistant-run-model-error', params: { message: String(abort.message ?? '').slice(0, 500) } }\n if (abort) return { code: 'assistant-run-model-error', params: { stopped: abort.type } }\n if (!baseText.trim()) return { code: 'assistant-run-empty-draft' }\n return undefined\n}\n\nconst TIMED_OUT = Symbol('timed-out')\n\nexport async function runAssistantUnattended(input: UnattendedRunInput, deps: UnattendedRunDeps = defaultDeps()): Promise<AssistantRun> {\n checkInput(input)\n const { domain, run } = input\n const mode = getAssistantMode(input.mode.kind)\n const limits = unattendedLimits(mode?.unattended)\n const startedAt = deps.now()\n /* The account as stored now - read once, and judged after the occurrence is held. */\n const account = await deps.runAs.findUser(input.runAs)\n\n const { claimed, row } = await deps.store.claim(\n { domainId: domain.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt },\n {\n name: `${run.source} ${run.subject} ${run.scheduledAt.toISOString()}`,\n modeKind: input.mode.kind,\n modeTarget: input.mode.target,\n runAsId: account?.id,\n startedAt,\n deadlineAt: new Date(startedAt.valueOf() + limits.timeoutMs + ABANDON_MARGIN_MS)\n },\n startedAt\n )\n /* Succeeded, or running elsewhere and still alive - the caller gets that row and the model is not called again. */\n if (!claimed) return row\n\n const held = { id: row.id!, attempt: row.attempt }\n const end = (ending: RunEnding) => deps.store.finish(held, { finishedAt: deps.now(), ...ending })\n const refuse = (code: string, params?: any) => end({ status: 'refused', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null } as any)\n const fail = (code: string, params?: any, spent?: RunEnding) =>\n end({ status: 'failed', outcomeCode: code, outcomeParams: params ? JSON.stringify(params) : null, ...spent } as any)\n\n if (!mode) return refuse('unknown-assistant-mode', { kind: input.mode.kind })\n if (!mode.unattended) return refuse('assistant-mode-not-unattended', { kind: mode.kind })\n\n const judged = await judgeRunAs(account, domain, deps.runAs)\n if ('refused' in judged) return refuse(judged.refused, { userId: input.runAs })\n const user = judged.user\n\n let agentClient: AgentClient\n try {\n agentClient = await deps.clientFor({ domain: domain as any, user, modeKind: mode.kind })\n } catch (error: any) {\n /* No AI here, or not the agent asked for - nothing ran. Its own code, as the conversation door says it. */\n if (error?.code) return refuse(String(error.code), error.params)\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) })\n }\n\n const { client, spend } = budgetedClient(agentClient, limits)\n const reads: ToolCallRecord[] = []\n let over = false\n const state: any = { domain, user, lng: input.lng ?? user.locale, aiAgentId: agentClient.agent.id }\n const categories = unattendedToolCategories(mode)\n const systemPrompt = unattendedPrompt(mode.systemPrompt, state.lng)\n const messages = [{ role: 'user' as const, content: input.instruction }]\n\n const abort = new AbortController()\n let timer: ReturnType<typeof setTimeout> | undefined\n const timeout = new Promise<typeof TIMED_OUT>(resolve => {\n timer = setTimeout(() => {\n abort.abort()\n resolve(TIMED_OUT)\n }, limits.timeoutMs)\n })\n\n const spent = (): RunEnding => ({\n reads: JSON.stringify(reads),\n agentId: agentClient.agent.id,\n clientId: agentClient.id,\n modelCalls: spend.modelCalls,\n inputTokens: spend.inputTokens,\n outputTokens: spend.outputTokens\n })\n\n let result: any\n try {\n result = await Promise.race([\n registryAssistantChat({\n client,\n messages,\n systemPrompt,\n toolCategories: categories,\n mode: { kind: mode.kind, ...(input.mode.target ? { target: input.mode.target } : {}) },\n toolAccess: deps.toolAccess(state),\n hostContext: input.hostContext,\n state,\n temperature: mode.temperature,\n maxTokens: mode.maxTokens,\n requireToolOnFirstTurn: mode.requireToolOnFirstTurn,\n /* Each step is one request. A run that reaches the last step without an answer failed - no wrap-up call (§4). */\n maxIterations: limits.maxModelCalls,\n salvageOnMaxIterations: false,\n signal: abort.signal,\n onToolCall: record => {\n if (!over) reads.push(record)\n },\n run: { id: held.id, source: run.source, subject: run.subject, scheduledAt: run.scheduledAt.toISOString() },\n groundingSources: groundingSourcesFor({ systemPrompt, messages: messages as any, hostContext: input.hostContext })\n }),\n timeout\n ])\n } catch (error: any) {\n over = true\n clearTimeout(timer)\n if (error instanceof RunBudgetExceeded) return fail(error.code, error.params, spent())\n return fail('assistant-run-model-error', { message: String(error?.message ?? error).slice(0, 500) }, spent())\n }\n over = true\n clearTimeout(timer)\n\n if (result === TIMED_OUT) return fail('assistant-run-timeout', { timeoutMs: limits.timeoutMs }, spent())\n /* The loop turns a refused request into a provider error; the budget says which limit it was. */\n if (spend.exceeded) return fail('assistant-run-budget-exceeded', spend.exceeded, spent())\n\n const failure = failureOf(result.loop, result.baseText ?? '')\n if (failure) return fail(failure.code, failure.params, spent())\n\n const doors = doorsForAnswer(\n (result.loop.toolUsages ?? []).map((usage: any) => usage?.name).filter(Boolean),\n name => (findToolSpec(name, categories) as any)?.doors\n )\n return end({\n status: 'succeeded',\n draft: result.baseText,\n doors,\n cardsDropped: result.loop.proposals?.length ?? 0,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings).slice(0, 1024) : null,\n ...spent()\n } as any)\n}\n\n/** The stored run of an occurrence, for the application that scheduled it - read behind that application's own door. */\nexport async function findAssistantRun(identity: { domainId: string; source: string; subject: string; scheduledAt: Date } | { id: string }): Promise<AssistantRun | null> {\n const repository = getRepository(AssistantRun)\n if ('id' in identity) return repository.findOne({ where: { id: identity.id } })\n return repository.findOne({\n where: { domain: { id: identity.domainId }, source: identity.source, subject: identity.subject, scheduledAt: identity.scheduledAt } as any\n })\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The rules every unattended run follows, after the mode's persona (design/ai/unattended-run.md §3). The reader is the
|
|
4
|
+
* model, so the text is English (ADR-0061 A11); the draft is written in the owner's language.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.UNATTENDED_RULES = void 0;
|
|
8
|
+
exports.languageName = languageName;
|
|
9
|
+
exports.unattendedPrompt = unattendedPrompt;
|
|
10
|
+
const LANGUAGES = { ko: 'Korean', en: 'English', ja: 'Japanese', zh: 'Chinese', ms: 'Malay', vi: 'Vietnamese' };
|
|
11
|
+
function languageName(lng) {
|
|
12
|
+
const code = String(lng ?? '').trim().toLowerCase();
|
|
13
|
+
if (!code)
|
|
14
|
+
return 'English';
|
|
15
|
+
return LANGUAGES[code] ?? LANGUAGES[code.split(/[-_]/)[0]] ?? `the language of locale "${code}"`;
|
|
16
|
+
}
|
|
17
|
+
exports.UNATTENDED_RULES = [
|
|
18
|
+
'No person is reading this as it is written. It runs on a schedule, and your answer is kept as a draft that its owner reads later.',
|
|
19
|
+
'- Read before you write. Every figure and fact in the draft comes from a tool result of this run.',
|
|
20
|
+
'- A read that failed or was refused is said as failed, with what the draft cannot say because of it. Never write it as "no change".',
|
|
21
|
+
'- Nothing you call is shown to anyone: there are no cards and no screens to open. Put what matters in the text.',
|
|
22
|
+
'- Do not ask questions - nobody will answer. Where something is missing, say what is missing.'
|
|
23
|
+
].join('\n');
|
|
24
|
+
function unattendedPrompt(persona, lng) {
|
|
25
|
+
return `${persona}\n\n${exports.UNATTENDED_RULES}\n- Write the draft in ${languageName(lng)}.`;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=unattended-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unattended-prompt.js","sourceRoot":"","sources":["../../../server/service/unattended-run/unattended-prompt.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAIH,oCAIC;AAUD,4CAEC;AAlBD,MAAM,SAAS,GAA2B,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,CAAA;AAEvI,SAAgB,YAAY,CAAC,GAAuB;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACnD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,2BAA2B,IAAI,GAAG,CAAA;AAClG,CAAC;AAEY,QAAA,gBAAgB,GAAG;IAC9B,mIAAmI;IACnI,mGAAmG;IACnG,qIAAqI;IACrI,iHAAiH;IACjH,+FAA+F;CAChG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ,SAAgB,gBAAgB,CAAC,OAAe,EAAE,GAAuB;IACvE,OAAO,GAAG,OAAO,OAAO,wBAAgB,0BAA0B,YAAY,CAAC,GAAG,CAAC,GAAG,CAAA;AACxF,CAAC","sourcesContent":["/*\n * The rules every unattended run follows, after the mode's persona (design/ai/unattended-run.md §3). The reader is the\n * model, so the text is English (ADR-0061 A11); the draft is written in the owner's language.\n */\n\nconst LANGUAGES: Record<string, string> = { ko: 'Korean', en: 'English', ja: 'Japanese', zh: 'Chinese', ms: 'Malay', vi: 'Vietnamese' }\n\nexport function languageName(lng: string | undefined): string {\n const code = String(lng ?? '').trim().toLowerCase()\n if (!code) return 'English'\n return LANGUAGES[code] ?? LANGUAGES[code.split(/[-_]/)[0]] ?? `the language of locale \"${code}\"`\n}\n\nexport const UNATTENDED_RULES = [\n 'No person is reading this as it is written. It runs on a schedule, and your answer is kept as a draft that its owner reads later.',\n '- Read before you write. Every figure and fact in the draft comes from a tool result of this run.',\n '- A read that failed or was refused is said as failed, with what the draft cannot say because of it. Never write it as \"no change\".',\n '- Nothing you call is shown to anyone: there are no cards and no screens to open. Put what matters in the text.',\n '- Do not ask questions - nobody will answer. Where something is missing, say what is missing.'\n].join('\\n')\n\nexport function unattendedPrompt(persona: string, lng: string | undefined): string {\n return `${persona}\\n\\n${UNATTENDED_RULES}\\n- Write the draft in ${languageName(lng)}.`\n}\n"]}
|