thursday-agent 0.6.0 → 0.7.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +3 -3
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +1 -1
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/missing/route.js +2 -2
- package/.next/server/app/api/workspace/missing/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page/server-reference-manifest.json +42 -42
- package/.next/server/app/page.js +5 -4
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0-06t3f._.js +8 -0
- package/.next/server/chunks/{[root-of-the-server]__049f521._.js → [root-of-the-server]__06l05n_._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_ue8oe._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__0ye3da2._.js → [root-of-the-server]__0btqrct._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0c9fgca._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0zp35ul._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1kv8p3e._.js +1 -1
- package/.next/server/chunks/_02szwk8._.js +1 -1
- package/.next/server/chunks/_06c9kie._.js +24 -24
- package/.next/server/chunks/_0i55en5._.js +6 -6
- package/.next/server/chunks/_0p56fn0._.js +1 -1
- package/.next/server/chunks/_11ujpya._.js +1 -1
- package/.next/server/chunks/_1dbixxz._.js +1 -1
- package/.next/server/chunks/_1dc_d1q._.js +16 -16
- package/.next/server/chunks/_1gnjfli._.js +3 -0
- package/.next/server/chunks/_1klbcft._.js +1 -1
- package/.next/server/chunks/features_0-cnt0l._.js +1 -1
- package/.next/server/chunks/features_0hdaci0._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0-e011f._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0pd8yr7._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1nygfik._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1sdphb0._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1fll8dc._.js → [root-of-the-server]__1vdmfbe._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__1waa3rc._.js +34 -49
- package/.next/server/chunks/ssr/{_1anqots._.js → _0-2qsxo._.js} +2 -2
- package/.next/server/chunks/ssr/_02oc_h5._.js +9 -9
- package/.next/server/chunks/ssr/_05yghx6._.js +1 -1
- package/.next/server/chunks/ssr/_07nbwap._.js +3 -0
- package/.next/server/chunks/ssr/_08c4z-9._.js +1 -1
- package/.next/server/chunks/ssr/_0_qfdlw._.js +1 -1
- package/.next/server/chunks/ssr/_0b6_jv-._.js +3 -0
- package/.next/server/chunks/ssr/_0hxt2ul._.js +1 -1
- package/.next/server/chunks/ssr/_0i_6q1w._.js +1 -1
- package/.next/server/chunks/ssr/{_18xqmny._.js → _0no7i1o._.js} +2 -2
- package/.next/server/chunks/ssr/_0tzg--b._.js +3 -0
- package/.next/server/chunks/ssr/{_1ztk7p6._.js → _114ylv7._.js} +3 -3
- package/.next/server/chunks/ssr/{_1k0spz7._.js → _12c-iwh._.js} +2 -2
- package/.next/server/chunks/ssr/_12w42gi._.js +1 -1
- package/.next/server/chunks/ssr/{_0eaog6_._.js → _167hchk._.js} +2 -2
- package/.next/server/chunks/ssr/{_1x9pskj._.js → _1_x5spz._.js} +3 -3
- package/.next/server/chunks/ssr/{_0dr-ddg._.js → _1a61fc9._.js} +2 -2
- package/.next/server/chunks/ssr/_1jmz33w._.js +1 -1
- package/.next/server/chunks/ssr/{_1y6b5mr._.js → _1w_t7h7._.js} +2 -2
- package/.next/server/chunks/ssr/{_0dh2e1j._.js → _1wyfaq_._.js} +7 -7
- package/.next/server/chunks/ssr/{[root-of-the-server]__0bxsfyn._.js → _200gt-m._.js} +4 -3
- package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/{_0c9e5w6._.js → features_workspace_00jz2hu._.js} +3 -4
- package/.next/server/chunks/ssr/{node_modules_0746rzi._.js → node_modules_02ai7br._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0vsxbzp._.js → node_modules_02deexj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1g1eihe._.js → node_modules_07za262._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0332gw4._.js → node_modules_0_s9get._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1gpednh._.js → node_modules_0n3c7h0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0t-9nup._.js → node_modules_0od6jwr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0mxvg8h._.js → node_modules_0rdt7pk._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0vv0-fg._.js → node_modules_0w1z7kz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1d53yiq._.js → node_modules_0wdupfz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1u6w1pb._.js → node_modules_1720w0a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1gu1pph._.js → node_modules_18lsg7d._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1he1w8_._.js → node_modules_1b94fxf._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0qbfyjh._.js → node_modules_1ecdo1s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0qapgjr._.js → node_modules_1fa9ag7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0d5uzof._.js → node_modules_1fv-_si._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1lnjapm._.js → node_modules_1hf461g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1-urho9._.js → node_modules_1m71ta-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1lynj9m._.js → node_modules_1n93n-g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fr-ml8._.js → node_modules_1n9lqu9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_04vxchl._.js → node_modules_1npnsvd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0exkwcm._.js → node_modules_1rn2r0n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_022sipm._.js → node_modules_1vq-mfk._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00_51_v._.js → node_modules_1vzyw1t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0wsvp00._.js → node_modules_1w359w4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xn6saw._.js → node_modules_1yajbl6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16pi_dd._.js → node_modules_20eqhy8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_067_exa._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_019sbis._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0nrrdaq._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0s6piq3._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_21a5haa._.js → node_modules_@vercel_oidc_dist_05itbb1._.js} +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +43 -43
- package/.next/static/chunks/{0dr16v67vj4n7.js → 07vqbk_9t4em4.js} +2 -2
- package/.next/static/chunks/{2nvepds1fu2ux.js → 08krja1zx7nqg.js} +1 -1
- package/.next/static/chunks/{42wcg3wfg_m_p.js → 0qcuwdk132jjt.js} +1 -1
- package/.next/static/chunks/{2_pnccjp6j1ca.js → 0z69gvysmave7.js} +1 -1
- package/.next/static/chunks/12fvz1w5ows75.js +1 -0
- package/.next/static/chunks/14t03ned286iu.js +1 -0
- package/.next/static/chunks/{2jlz8zp4jax_m.js → 156xxbkrz5lzu.js} +1 -1
- package/.next/static/chunks/1fz14reod4lft.js +1 -0
- package/.next/static/chunks/{0joan-69kbspd.js → 1g23dbabl-vkr.js} +1 -1
- package/.next/static/chunks/{18mkhstgiqcbt.js → 1qy_rc3oxfxok.js} +1 -1
- package/.next/static/chunks/1zea7qdl-7365.js +1 -0
- package/.next/static/chunks/1zvqumkg7kmo0.css +1 -0
- package/.next/static/chunks/26hpqt3y6bogl.js +12 -0
- package/.next/static/chunks/{3zk6q3-3f9swq.js → 2_j5mqm6d-z18.js} +1 -1
- package/.next/static/chunks/{1vj_5bnbxx84e.js → 2dt6tu7a22svs.js} +1 -1
- package/.next/static/chunks/2h5ls3d86kya6.js +1 -0
- package/.next/static/chunks/2wc6y1ht6dfuy.js +1 -0
- package/.next/static/chunks/{1i-u2wzub_to1.js → 2xr0zpll7thyi.js} +6 -6
- package/.next/static/chunks/{26ql4y-43xwhp.js → 31jztc-yq30m7.js} +1 -1
- package/.next/static/chunks/391af0i8eljzk.js +1 -0
- package/.next/static/chunks/{1kp5nfp06nj9h.js → 3p2s2cjokx1aa.js} +2 -2
- package/.next/static/chunks/{1wlo979mx-y3w.js → 41s3fml5oivsa.js} +1 -1
- package/README.md +60 -19
- package/docs/how-it-works.md +2 -0
- package/docs/live-calls.md +38 -33
- package/features/ai/load-tools.ts +4 -2
- package/features/ai/prompts/bot.prompt.ts +5 -5
- package/features/ai/prompts/live.prompt.ts +36 -132
- package/features/ai/prompts/prompt-helper.ts +5 -4
- package/features/ai/prompts/thursday.prompt.ts +75 -54
- package/features/ai/tools/call.tool.ts +32 -4
- package/features/ai/tools/tool-name.ts +1 -0
- package/features/bot/bot.runner.ts +5 -5
- package/features/thursday/ascii.const.ts +74 -0
- package/features/thursday/components/ascii-orb.tsx +173 -30
- package/features/thursday/components/face.tsx +6 -1
- package/features/thursday/components/side-captions.tsx +273 -0
- package/features/thursday/components/tab-state.tsx +11 -3
- package/features/thursday/components/thursday-setting.tsx +6 -5
- package/features/thursday/components/thursday.tsx +227 -251
- package/features/thursday/face.store.ts +5 -0
- package/features/thursday/thursday.action.ts +4 -6
- package/features/thursday/thursday.schema.ts +10 -5
- package/features/thursday/thursday.store.ts +3 -0
- package/features/thursday/use-thursday.ts +461 -392
- package/features/workspace/file-kind.ts +9 -0
- package/hooks/use-hotkey.ts +3 -2
- package/lib/live/live.schema.ts +0 -21
- package/lib/live/live.server.ts +1 -3
- package/package.json +1 -1
- package/scripts/artifact-paths.test.mts +13 -0
- package/scripts/live-session.test.mts +56 -66
- package/.next/server/chunks/[root-of-the-server]__0g-ueei._.js +0 -8
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1kipdeo._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__172cq4x._.js +0 -3
- package/.next/server/chunks/ssr/_03_928f._.js +0 -3
- package/.next/server/chunks/ssr/_0g_rlog._.js +0 -3
- package/.next/server/chunks/ssr/_123ttsf._.js +0 -3
- package/.next/static/chunks/10k2xvap03z20.css +0 -1
- package/.next/static/chunks/1t9qms5pwwa3n.js +0 -1
- package/.next/static/chunks/2a51-ywz9b2a3.js +0 -1
- package/.next/static/chunks/2fvdgxih7x7hr.js +0 -1
- package/.next/static/chunks/3addj3i4-izma.js +0 -1
- package/.next/static/chunks/3gfh7nxwusbt0.js +0 -1
- package/.next/static/chunks/3gwa44cvz4qoi.js +0 -1
- package/.next/static/chunks/3zkg5iup3v4a2.js +0 -12
- package/.next/static/chunks/405m0bqtoihev.js +0 -1
- /package/.next/static/{0qzGoKn2wTERVZtblX1tp → yyRVBc0xxYQsh3GpbQX9b}/_buildManifest.js +0 -0
- /package/.next/static/{0qzGoKn2wTERVZtblX1tp → yyRVBc0xxYQsh3GpbQX9b}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{0qzGoKn2wTERVZtblX1tp → yyRVBc0xxYQsh3GpbQX9b}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CALL_EXEC_TIMEOUT_MS, RECENT_CALL } from "@/config";
|
|
2
2
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
3
|
-
import { listJobBots } from "@/features/bot/bot.query";
|
|
3
|
+
import { listJobBots, readBotMemoryOn } from "@/features/bot/bot.query";
|
|
4
4
|
import type { JobBot } from "@/features/bot/bot.schema";
|
|
5
5
|
import { type CallJob, listCallJobs } from "@/features/bot/thread.query";
|
|
6
6
|
import {
|
|
@@ -41,12 +41,12 @@ import {
|
|
|
41
41
|
} from "./prompt-helper";
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Everything the call's Responses backend hears
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* Everything the call's Responses backend hears: who Thursday is and how a call ends (the words
|
|
45
|
+
* the voice opens with too), then the work the voice hands over — memory with ids, background
|
|
46
|
+
* work with the roster, this computer — what to return, and the last calls with their jobs, which
|
|
47
|
+
* only the backend reads. Nothing here is about how to talk. The loader is the table of contents,
|
|
48
|
+
* and empty chapters are dropped. Shares no sentence with bot.prompt. Assembled on every call,
|
|
49
|
+
* never cached.
|
|
50
50
|
*
|
|
51
51
|
* @param backendPrompt Settings › Thursday › Backend instructions, added last.
|
|
52
52
|
*/
|
|
@@ -54,20 +54,30 @@ export async function loadThursdayPrompt(
|
|
|
54
54
|
backendPrompt?: string | null,
|
|
55
55
|
): Promise<string> {
|
|
56
56
|
const sandbox = await openWorkspace();
|
|
57
|
-
const [
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
const [
|
|
58
|
+
skills,
|
|
59
|
+
index,
|
|
60
|
+
carried,
|
|
61
|
+
open,
|
|
62
|
+
connected,
|
|
63
|
+
roster,
|
|
64
|
+
calls,
|
|
65
|
+
hers,
|
|
66
|
+
botMemory,
|
|
67
|
+
] = await Promise.all([
|
|
68
|
+
loadSkills(sandbox),
|
|
69
|
+
listNoteIndex(),
|
|
70
|
+
// Facts carried into every call without opening a note
|
|
71
|
+
listAlwaysLoaded(),
|
|
72
|
+
// Written out in the prompt, which is not the user asking for them: no read counted
|
|
73
|
+
readNotes(MEMORY_ALWAYS_LISTED, { touch: false }),
|
|
74
|
+
listConnectedToolNames(),
|
|
75
|
+
listJobBots(),
|
|
76
|
+
listRecentTurns(RECENT_CALL.rows),
|
|
77
|
+
// Whether the call was handed `load_skill` (Settings › Thursday, load-tools)
|
|
78
|
+
readCallSkillsOn(),
|
|
79
|
+
readBotMemoryOn(),
|
|
80
|
+
]);
|
|
71
81
|
// The jobs those calls opened, folded into the transcript below
|
|
72
82
|
const jobs = await listCallJobs(calls.map((call) => call.callId));
|
|
73
83
|
|
|
@@ -75,11 +85,14 @@ export async function loadThursdayPrompt(
|
|
|
75
85
|
const text = [
|
|
76
86
|
thursdayIdentity(),
|
|
77
87
|
callEnding(),
|
|
78
|
-
conversation(),
|
|
79
88
|
memory(index, carried, open.notes),
|
|
80
89
|
// A skill is named once, on the side that can read it: this computer's chapter
|
|
81
90
|
// when the setting hands the call the tool, the bots' reach when it does not
|
|
82
|
-
backgroundWork(
|
|
91
|
+
backgroundWork(
|
|
92
|
+
roster,
|
|
93
|
+
reachNames(hers ? [] : skills, connected),
|
|
94
|
+
botMemory,
|
|
95
|
+
),
|
|
83
96
|
thisComputer(sandbox.cwd, hers ? skills : []),
|
|
84
97
|
result(),
|
|
85
98
|
earlierCalls(calls, jobs),
|
|
@@ -95,25 +108,13 @@ export async function loadThursdayPrompt(
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
|
-
* What
|
|
99
|
-
*
|
|
100
|
-
* gets back into `thread`.
|
|
111
|
+
* What goes back is said aloud, so it is what a tool or a note confirmed and nothing more — or
|
|
112
|
+
* the one question the voice puts to the user before work is handed over (backgroundWork).
|
|
101
113
|
*/
|
|
102
|
-
function conversation(): string {
|
|
103
|
-
return `## Voice conversation context
|
|
104
|
-
|
|
105
|
-
You are on a live voice call with the user. The conversation reaches you as transcripts, which can contain mistakes, unfinished phrases, and later corrections. Use the latest context and verified records. If a needed detail is still unclear, ask for that detail instead of guessing. Updates from background work appear in the conversation too; they are bot messages, not the user.
|
|
106
|
-
|
|
107
|
-
When the user wants to end the call, \`${TOOL_NAMES.end_call}\` hangs up the phone; nothing else ends it.
|
|
108
|
-
|
|
109
|
-
Tool results, notes and these instructions are in English, which says nothing about the user's language.`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** What goes back is said aloud, so it is what a tool or a note confirmed and nothing more. */
|
|
113
114
|
function result(): string {
|
|
114
115
|
return `## Return the result
|
|
115
116
|
|
|
116
|
-
Return the relevant facts, whether the task is complete, and what comes next — for a job you handed over, who has it. Use confirmed values from tool results and the notes above, and never invent a successful action. What you return is said aloud: keep it short and plain.`;
|
|
117
|
+
Return the relevant facts, whether the task is complete, and what comes next — for a job you handed over, who has it — or the one question the user has to answer first. Use confirmed values from tool results and the notes above, and never invent a successful action. What you return is said aloud: keep it short and plain.`;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/** Settings › Thursday › Backend instructions; no heading when empty. */
|
|
@@ -128,8 +129,10 @@ ${backendPrompt.trim()}`
|
|
|
128
129
|
|
|
129
130
|
/**
|
|
130
131
|
* Profile and preferences written out, carried facts, the listing, and what goes in. What is
|
|
131
|
-
* worth keeping is the model's call; how a fact is written — carried,
|
|
132
|
-
*
|
|
132
|
+
* worth keeping is the model's call; how a fact is written — carried, dated — is the tool's
|
|
133
|
+
* schema to say. Merging is said here too: left to the `replaces` description alone, facts on
|
|
134
|
+
* one subject piled up beside each other (09-17). Merging loses nothing; deleting does, so
|
|
135
|
+
* `memory_forget` stays for what the user names.
|
|
133
136
|
*/
|
|
134
137
|
function memory(
|
|
135
138
|
index: MemoryIndexEntry[],
|
|
@@ -138,7 +141,16 @@ function memory(
|
|
|
138
141
|
open: MemoryNoteView[],
|
|
139
142
|
): string {
|
|
140
143
|
// Ages ride on the listing only when there is too much to hold: they are what to drop by
|
|
141
|
-
const { crowded } = tidying(index);
|
|
144
|
+
const { crowded, heavy } = tidying(index);
|
|
145
|
+
const heaviest = [...heavy]
|
|
146
|
+
.sort((a, b) => b.factCount - a.factCount)
|
|
147
|
+
.slice(0, 3)
|
|
148
|
+
.map((note) => note.path);
|
|
149
|
+
// Past MEMORY_LIMITS, settling it with the user; the voice opens nothing about it
|
|
150
|
+
const tidy =
|
|
151
|
+
crowded || heavy.length
|
|
152
|
+
? `\n\nSaved memory has grown past what it holds well${heaviest.length ? ` (${heaviest.join(", ")})` : ""}: say so once in what you return, go through what looks out of date with the user, and forget only what they name.`
|
|
153
|
+
: "";
|
|
142
154
|
// A note written out here is left off the listing, and so are its carried lines
|
|
143
155
|
const written = new Set(open.map((note) => note.path));
|
|
144
156
|
const carriedIds = new Set(carried.map((fact) => fact.id));
|
|
@@ -163,13 +175,13 @@ ${carriedLines(elsewhere)}`
|
|
|
163
175
|
${noteLines(
|
|
164
176
|
index.filter((note) => !written.has(note.path)),
|
|
165
177
|
crowded,
|
|
166
|
-
)}
|
|
178
|
+
)}${tidy}
|
|
167
179
|
|
|
168
180
|
Open a note before answering out of it; a topic not listed is one you know nothing about. A fact marked \`said\` came from a call; \`${TOOL_NAMES.memory_conversation}\` opens that call when the line alone cannot answer.
|
|
169
181
|
|
|
170
|
-
Keep what the user tells you as it comes up, with \`${TOOL_NAMES.memory_remember}\`, without waiting to be asked: what they actually said, never a guess,
|
|
182
|
+
Keep what the user tells you as it comes up, with \`${TOOL_NAMES.memory_remember}\`, without waiting to be asked: what they actually said, never a guess, nothing they asked you not to keep, and from a bot's report only what it confirmed about them.
|
|
171
183
|
|
|
172
|
-
A
|
|
184
|
+
**Keep memory clean as you write.** A fact that repeats, narrows or changes one already in the note replaces it, merged into one line, rather than sitting beside it. A later call finds a note only by its path, its line and the names in quotes: keep the line true, and give something new its own path below.
|
|
173
185
|
|
|
174
186
|
${MEMORY_PATHS.map((entry) => `- ${entry.path} — ${entry.of}`).join("\n")}`;
|
|
175
187
|
|
|
@@ -230,14 +242,25 @@ ${skillLines(skills, { short: true })}`;
|
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
/**
|
|
233
|
-
* Background work: who is there, what they can reach,
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
245
|
+
* Background work: who is there, what they can reach, that a job is a thread, and asking once
|
|
246
|
+
* before handing over. Capability is stated as fact; without it the model refuses instead of
|
|
247
|
+
* delegating. The user may know bots exist but not how a thread carries on or that a bot keeps
|
|
248
|
+
* its own memory, so the backend names the choice rather than making it silently. A bot gets
|
|
249
|
+
* none of the carried lines (bot.prompt memory), so how the user wants work done reaches it only
|
|
250
|
+
* through the request. No list of running jobs: jobs move during the call.
|
|
237
251
|
*/
|
|
238
|
-
function backgroundWork(
|
|
252
|
+
function backgroundWork(
|
|
253
|
+
roster: JobBot[],
|
|
254
|
+
reach: string,
|
|
255
|
+
/** Settings › Bots › memory: whether a bot is shown its own (bot.prompt ownMemory). */
|
|
256
|
+
botMemory: boolean,
|
|
257
|
+
): string {
|
|
239
258
|
if (roster.length === 0) return "";
|
|
240
259
|
|
|
260
|
+
const kept = botMemory
|
|
261
|
+
? " Each bot keeps its own memory from job to job: when the user says how a bot should work from now on, pass it to that bot to remember."
|
|
262
|
+
: "";
|
|
263
|
+
|
|
241
264
|
return `## Background work
|
|
242
265
|
|
|
243
266
|
Who you hand work to — the names \`${TOOL_NAMES.delegate}\` takes.
|
|
@@ -246,13 +269,11 @@ ${roster.map((bot) => `- **${bot.name}** — ${bot.description}`).join("\n")}${
|
|
|
246
269
|
reach ? `\n\nWhat bots can reach for: ${reach}.` : ""
|
|
247
270
|
}
|
|
248
271
|
|
|
249
|
-
**A bot can take on almost anything
|
|
250
|
-
|
|
251
|
-
**Anything that takes more than a few seconds is a bot's**; a note, a look at a file or one command is yours. Write the request in the user's own words, with what it stands on — including how they told you they want work done — and nothing they did not say.
|
|
272
|
+
**A bot can take on almost anything, and anything that takes more than a few seconds is a bot's**; a note, a look at a file or one command is yours. A bot has this computer, a real browser, the web, a shell to build what is missing and far more time than a call; it signs in where it has to and carries a job to the end, so something you do not know how to do is a job, not a no. Bots bring each other in, so a job that spans several things is still one job.${kept}
|
|
252
273
|
|
|
253
274
|
**A job is a thread.** Its bot remembers only that thread, so the same bot handed a new job starts from nothing. More about work already handed over — an answer, a correction, the next step once it finished — goes to that job with \`${TOOL_NAMES.thread}\`; a new request is a new job. Before answering about work, read it with \`${TOOL_NAMES.thread}\` \`status\`.
|
|
254
275
|
|
|
255
|
-
|
|
276
|
+
**Ask once before handing work over.** Return in one line what you would do — which bot, and whether it starts a new thread or carries on one already open — as a question to the user; when they have already said, or leave it to you, decide and send it. Write the request in the user's own words, with what it stands on — including how they told you they want work done — and nothing they did not say.
|
|
256
277
|
|
|
257
|
-
When the user wants to see a job, open it on their screen with \`${TOOL_NAMES.thread}\` \`open\`. Once you have explained enough of how a job ended, mark it with \`${TOOL_NAMES.thread}\` \`seen\`, or leave it for the user to open; never mention seen to them.`;
|
|
278
|
+
Updates and questions from jobs reach the conversation by themselves, with their thread and question id; they come from bots, not the user. When the user wants to see a job, open it on their screen with \`${TOOL_NAMES.thread}\` \`open\`. Once you have explained enough of how a job ended, mark it with \`${TOOL_NAMES.thread}\` \`seen\`, or leave it for the user to open; never mention seen to them.`;
|
|
258
279
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { tool } from "ai";
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
import { TOOL_NAMES } from "@/features/ai/tools/tool-name";
|
|
4
|
+
import {
|
|
5
|
+
FACE_WORD_MARKS,
|
|
6
|
+
FACE_WORD_MAX,
|
|
7
|
+
} from "@/features/thursday/ascii.const";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
|
-
* The call lives in the page, so
|
|
7
|
-
* page supplies the behaviour (use-thursday). loadTools declares
|
|
8
|
-
* `/api/tool-call` refuses
|
|
10
|
+
* The call lives in the page, so these tools have no server-side execute; the
|
|
11
|
+
* page supplies the behaviour (use-thursday). loadTools declares them too so
|
|
12
|
+
* `/api/tool-call` refuses them by name rather than as unknown tools.
|
|
9
13
|
*/
|
|
10
14
|
export const endCallSpec = {
|
|
11
15
|
name: TOOL_NAMES.end_call,
|
|
@@ -21,4 +25,28 @@ export const endCallTool = tool({
|
|
|
21
25
|
inputSchema: endCallSpec.parameters,
|
|
22
26
|
});
|
|
23
27
|
|
|
24
|
-
export const
|
|
28
|
+
export const emoteSpec = {
|
|
29
|
+
name: TOOL_NAMES.emote,
|
|
30
|
+
description: "Show a short word on your face for a few seconds.",
|
|
31
|
+
parameters: z.object({
|
|
32
|
+
text: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe(
|
|
35
|
+
`Up to ${FACE_WORD_MAX} characters: A-Z, 0-9, space and ${FACE_WORD_MARKS.join(" ")}.`,
|
|
36
|
+
),
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Deliberately has no `execute`. */
|
|
41
|
+
export const emoteTool = tool({
|
|
42
|
+
description: emoteSpec.description,
|
|
43
|
+
inputSchema: emoteSpec.parameters,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/** The tools that act on the call itself. `emote` only when the face can draw a word (the ascii orb). */
|
|
47
|
+
export function callTools(faceWords: boolean) {
|
|
48
|
+
return {
|
|
49
|
+
[TOOL_NAMES.end_call]: endCallTool,
|
|
50
|
+
...(faceWords ? { [TOOL_NAMES.emote]: emoteTool } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { setTimeout as wait } from "node:timers/promises";
|
|
2
2
|
import type { ModelMessage } from "ai";
|
|
3
3
|
import { appEvents, presence } from "@/app/api/events/app-event.server";
|
|
4
|
-
import { BOT_RUN,
|
|
4
|
+
import { BOT_RUN, WORKSPACE_KEEP } from "@/config";
|
|
5
5
|
import { isProviderRefusal, modelErrorToString } from "@/features/ai/model";
|
|
6
6
|
import { buildThreadOpening } from "@/features/ai/prompts/bot.prompt";
|
|
7
7
|
import { isAnyCallLive } from "@/features/thursday/thursday.query";
|
|
8
|
-
import { pathsIn } from "@/features/workspace/file-kind";
|
|
8
|
+
import { opensOnFinish, pathsIn } from "@/features/workspace/file-kind";
|
|
9
9
|
import {
|
|
10
10
|
botBrowserSession,
|
|
11
11
|
closeHiddenBrowser,
|
|
@@ -228,9 +228,9 @@ async function drive(work: RoomWork, signal: AbortSignal) {
|
|
|
228
228
|
if (thread?.status === "done") {
|
|
229
229
|
if (!(await isAnyCallLive()))
|
|
230
230
|
desktopNotify(thread.label, thread.outcome ?? "");
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
const path = (await filesOnDisk(pathsIn(thread.outcome ?? ""), null)).find(
|
|
232
|
+
opensOnFinish,
|
|
233
|
+
);
|
|
234
234
|
if (path)
|
|
235
235
|
appEvents.emit({
|
|
236
236
|
type: "artifact",
|
|
@@ -110,6 +110,80 @@ export const CHAR_RATE = 2.2;
|
|
|
110
110
|
/** Emoji changes per second; emoji are expensive to draw. */
|
|
111
111
|
export const EMOJI_CHAR_RATE = 0.6;
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Letters for words on the face (ERROR, and what `emote` shows): five rows, `#`
|
|
115
|
+
* is ink. A space is a narrow gap; lowercase is drawn in capitals.
|
|
116
|
+
*/
|
|
117
|
+
export const LETTERS: Record<string, readonly string[]> = {
|
|
118
|
+
A: [".##.", "#..#", "####", "#..#", "#..#"],
|
|
119
|
+
B: ["###.", "#..#", "###.", "#..#", "###."],
|
|
120
|
+
C: [".###", "#...", "#...", "#...", ".###"],
|
|
121
|
+
D: ["###.", "#..#", "#..#", "#..#", "###."],
|
|
122
|
+
E: ["####", "#...", "###.", "#...", "####"],
|
|
123
|
+
F: ["####", "#...", "###.", "#...", "#..."],
|
|
124
|
+
G: [".###", "#...", "#.##", "#..#", ".###"],
|
|
125
|
+
H: ["#..#", "#..#", "####", "#..#", "#..#"],
|
|
126
|
+
I: ["###", ".#.", ".#.", ".#.", "###"],
|
|
127
|
+
J: ["..##", "...#", "...#", "#..#", ".##."],
|
|
128
|
+
K: ["#..#", "#.#.", "##..", "#.#.", "#..#"],
|
|
129
|
+
L: ["#...", "#...", "#...", "#...", "####"],
|
|
130
|
+
M: ["#...#", "##.##", "#.#.#", "#...#", "#...#"],
|
|
131
|
+
N: ["#..#", "##.#", "#.##", "#..#", "#..#"],
|
|
132
|
+
O: [".##.", "#..#", "#..#", "#..#", ".##."],
|
|
133
|
+
P: ["###.", "#..#", "###.", "#...", "#..."],
|
|
134
|
+
Q: [".##.", "#..#", "#..#", "#.#.", ".#.#"],
|
|
135
|
+
R: ["###.", "#..#", "###.", "#.#.", "#..#"],
|
|
136
|
+
S: [".###", "#...", ".##.", "...#", "###."],
|
|
137
|
+
T: ["###", ".#.", ".#.", ".#.", ".#."],
|
|
138
|
+
U: ["#..#", "#..#", "#..#", "#..#", ".##."],
|
|
139
|
+
V: ["#...#", "#...#", ".#.#.", ".#.#.", "..#.."],
|
|
140
|
+
W: ["#...#", "#...#", "#.#.#", "##.##", "#...#"],
|
|
141
|
+
X: ["#..#", "#..#", ".##.", "#..#", "#..#"],
|
|
142
|
+
Y: ["#.#", "#.#", ".#.", ".#.", ".#."],
|
|
143
|
+
Z: ["####", "...#", ".##.", "#...", "####"],
|
|
144
|
+
"0": [".##.", "#.##", "#..#", "##.#", ".##."],
|
|
145
|
+
"1": [".#.", "##.", ".#.", ".#.", "###"],
|
|
146
|
+
"2": ["###.", "...#", ".##.", "#...", "####"],
|
|
147
|
+
"3": ["###.", "...#", ".##.", "...#", "###."],
|
|
148
|
+
"4": ["#..#", "#..#", "####", "...#", "...#"],
|
|
149
|
+
"5": ["####", "#...", "###.", "...#", "###."],
|
|
150
|
+
"6": [".##.", "#...", "###.", "#..#", ".##."],
|
|
151
|
+
"7": ["####", "...#", "..#.", ".#..", ".#.."],
|
|
152
|
+
"8": [".##.", "#..#", ".##.", "#..#", ".##."],
|
|
153
|
+
"9": [".##.", "#..#", ".###", "...#", ".##."],
|
|
154
|
+
"!": ["#", "#", "#", ".", "#"],
|
|
155
|
+
"?": ["###.", "...#", ".##.", "....", ".#.."],
|
|
156
|
+
".": [".", ".", ".", ".", "#"],
|
|
157
|
+
",": ["..", "..", "..", ".#", "#."],
|
|
158
|
+
"'": ["#", "#", ".", ".", "."],
|
|
159
|
+
"-": ["...", "...", "###", "...", "..."],
|
|
160
|
+
"+": ["...", ".#.", "###", ".#.", "..."],
|
|
161
|
+
":": [".", "#", ".", "#", "."],
|
|
162
|
+
"(": [".#", "#.", "#.", "#.", ".#"],
|
|
163
|
+
")": ["#.", ".#", ".#", ".#", "#."],
|
|
164
|
+
"<": ["..#", ".#.", "#..", ".#.", "..#"],
|
|
165
|
+
">": ["#..", ".#.", "..#", ".#.", "#.."],
|
|
166
|
+
"^": [".#.", "#.#", "...", "...", "..."],
|
|
167
|
+
"~": ["....", ".#.#", "#.#.", "....", "...."],
|
|
168
|
+
"♥": [".#.#.", "#####", "#####", ".###.", "..#.."],
|
|
169
|
+
" ": ["..", "..", "..", "..", ".."],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** The longest word the face shows: eight letters still sit inside her body at the default glyph size. */
|
|
173
|
+
export const FACE_WORD_MAX = 8;
|
|
174
|
+
|
|
175
|
+
/** The marks a word may use besides A–Z, 0–9 and a space. */
|
|
176
|
+
export const FACE_WORD_MARKS = Object.keys(LETTERS).filter(
|
|
177
|
+
(char) => !/^[A-Z0-9 ]$/.test(char),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
/** The first character of `text` the face has no letter for, or null. */
|
|
181
|
+
export function undrawable(text: string) {
|
|
182
|
+
return (
|
|
183
|
+
Array.from(text.toUpperCase()).find((char) => !(char in LETTERS)) ?? null
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
113
187
|
/** Deterministic per-cell random in [0, 1): the same (x, y) always hashes the same. */
|
|
114
188
|
export function hash(x: number, y: number) {
|
|
115
189
|
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;
|