@poncho-ai/harness 0.22.0 → 0.23.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/.turbo/turbo-build.log +5 -5
- package/.turbo/turbo-lint.log +6 -0
- package/.turbo/turbo-test.log +135 -0
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +112 -30
- package/package.json +1 -1
- package/src/agent-parser.ts +7 -0
- package/src/harness.ts +34 -6
- package/src/memory.ts +4 -4
- package/src/state.ts +84 -23
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.
|
|
2
|
+
> @poncho-ai/harness@0.23.0 build /Users/cesar/Dev/latitude/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
12
|
-
[32mESM[39m ⚡️ Build success in
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m261.68 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 128ms
|
|
13
13
|
[34mDTS[39m Build start
|
|
14
|
-
[32mDTS[39m ⚡️ Build success in
|
|
15
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m27.
|
|
14
|
+
[32mDTS[39m ⚡️ Build success in 4463ms
|
|
15
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m27.40 KB[39m
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
> @poncho-ai/harness@0.16.1 test /Users/cesar/Dev/latitude/poncho-ai/packages/harness
|
|
3
|
+
> vitest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[7m[1m[36m RUN [39m[22m[27m [36mv1.6.1[39m [90m/Users/cesar/Dev/latitude/poncho-ai/packages/harness[39m
|
|
7
|
+
|
|
8
|
+
[32m✓[39m test/telemetry.test.ts [2m ([22m[2m3 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
9
|
+
[event] step:completed {"type":"step:completed","step":1,"duration":1}
|
|
10
|
+
[event] step:started {"type":"step:started","step":2}
|
|
11
|
+
[32m✓[39m test/schema-converter.test.ts [2m ([22m[2m27 tests[22m[2m)[22m[90m 19[2mms[22m[39m
|
|
12
|
+
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mdiscovers and calls tools over streamable HTTP[22m[39m
|
|
13
|
+
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
14
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
15
|
+
|
|
16
|
+
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mselects discovered tools by requested patterns[22m[39m
|
|
17
|
+
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
18
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
19
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
20
|
+
|
|
21
|
+
[32m✓[39m test/agent-parser.test.ts [2m ([22m[2m10 tests[22m[2m)[22m[90m 24[2mms[22m[39m
|
|
22
|
+
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
23
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":0,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
24
|
+
|
|
25
|
+
[90mstderr[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mskips discovery when bearer token env value is missing[22m[39m
|
|
26
|
+
[poncho][mcp] {"event":"auth.token_missing","server":"remote","tokenEnv":"MISSING_TOKEN_ENV"}
|
|
27
|
+
|
|
28
|
+
[90mstdout[2m | test/mcp.test.ts[2m > [22m[2mmcp bridge protocol transports[2m > [22m[2mreturns actionable errors for 403 permission failures[22m[39m
|
|
29
|
+
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
30
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
31
|
+
|
|
32
|
+
[32m✓[39m test/mcp.test.ts [2m ([22m[2m6 tests[22m[2m)[22m[90m 81[2mms[22m[39m
|
|
33
|
+
[32m✓[39m test/memory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 56[2mms[22m[39m
|
|
34
|
+
[32m✓[39m test/state.test.ts [2m ([22m[2m5 tests[22m[2m)[22m[90m 237[2mms[22m[39m
|
|
35
|
+
[32m✓[39m test/model-factory.test.ts [2m ([22m[2m4 tests[22m[2m)[22m[90m 2[2mms[22m[39m
|
|
36
|
+
[32m✓[39m test/agent-identity.test.ts [2m ([22m[2m2 tests[22m[2m)[22m[90m 43[2mms[22m[39m
|
|
37
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters default filesystem tools[22m[39m
|
|
38
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
39
|
+
|
|
40
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdisables write_file by default in production environment[22m[39m
|
|
41
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
42
|
+
|
|
43
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows disabling built-in tools via poncho.config.js[22m[39m
|
|
44
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
45
|
+
|
|
46
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
47
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
48
|
+
|
|
49
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports per-environment tool overrides[22m[39m
|
|
50
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
51
|
+
|
|
52
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not auto-register exported tool objects from skill scripts[22m[39m
|
|
53
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
54
|
+
|
|
55
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
56
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
57
|
+
|
|
58
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrefreshes skill metadata and tools in development mode[22m[39m
|
|
59
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
60
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"activate:beta","requestedPatterns":[]}
|
|
61
|
+
|
|
62
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
63
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
64
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"activate:obsolete","requestedPatterns":[]}
|
|
65
|
+
|
|
66
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mprunes removed active skills after refresh in development mode[22m[39m
|
|
67
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
68
|
+
|
|
69
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mdoes not refresh skills outside development mode[22m[39m
|
|
70
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
71
|
+
|
|
72
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
73
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
74
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"activate:alpha","requestedPatterns":[]}
|
|
75
|
+
|
|
76
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mclears active skills when skill metadata changes in development mode[22m[39m
|
|
77
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"skills:changed","requestedPatterns":[]}
|
|
78
|
+
|
|
79
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mlists skill scripts through list_skill_scripts[22m[39m
|
|
80
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
81
|
+
|
|
82
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns JavaScript/TypeScript skill scripts through run_skill_script[22m[39m
|
|
83
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
84
|
+
|
|
85
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mruns AGENT-scope scripts from root scripts directory[22m[39m
|
|
86
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
87
|
+
|
|
88
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mblocks path traversal in run_skill_script[22m[39m
|
|
89
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
90
|
+
|
|
91
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mrequires allowed-tools entries for non-standard script directories[22m[39m
|
|
92
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
93
|
+
|
|
94
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisters MCP tools dynamically for stacked active skills and supports deactivation[22m[39m
|
|
95
|
+
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":2}
|
|
96
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
97
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
98
|
+
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-a","requestedPatterns":["remote/a"],"registeredCount":1,"activeSkills":["skill-a"]}
|
|
99
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":2,"registeredCount":2,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
100
|
+
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-b","requestedPatterns":["remote/a","remote/b"],"registeredCount":2,"activeSkills":["skill-a","skill-b"]}
|
|
101
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":1}
|
|
102
|
+
[poncho][mcp] {"event":"tools.refreshed","reason":"deactivate:skill-a","requestedPatterns":["remote/b"],"registeredCount":1,"activeSkills":["skill-b"]}
|
|
103
|
+
|
|
104
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2msupports flat tool access config format[22m[39m
|
|
105
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
106
|
+
|
|
107
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mflat tool access takes priority over legacy defaults[22m[39m
|
|
108
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
109
|
+
|
|
110
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mbyEnvironment overrides flat tool access[22m[39m
|
|
111
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
112
|
+
|
|
113
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mregisterTools skips tools disabled via config[22m[39m
|
|
114
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
115
|
+
|
|
116
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mapproval access level registers the tool but marks it for approval[22m[39m
|
|
117
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
118
|
+
|
|
119
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mtools without approval config do not require approval[22m[39m
|
|
120
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
121
|
+
|
|
122
|
+
[90mstdout[2m | test/harness.test.ts[2m > [22m[2magent harness[2m > [22m[2mallows in-flight MCP calls to finish after skill deactivation[22m[39m
|
|
123
|
+
[poncho][mcp] {"event":"catalog.loaded","server":"remote","discoveredCount":1}
|
|
124
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"initialize","requestedPatterns":[]}
|
|
125
|
+
[poncho][mcp] {"event":"tools.selected","requestedPatternCount":1,"registeredCount":1,"filteredByPolicyCount":0,"filteredByIntentCount":0}
|
|
126
|
+
[poncho][mcp] {"event":"tools.refreshed","reason":"activate:skill-slow","requestedPatterns":["remote/slow"],"registeredCount":1,"activeSkills":["skill-slow"]}
|
|
127
|
+
[poncho][mcp] {"event":"tools.cleared","reason":"deactivate:skill-slow","requestedPatterns":[]}
|
|
128
|
+
|
|
129
|
+
[32m✓[39m test/harness.test.ts [2m ([22m[2m25 tests[22m[2m)[22m[90m 291[2mms[22m[39m
|
|
130
|
+
|
|
131
|
+
[2m Test Files [22m [1m[32m9 passed[39m[22m[90m (9)[39m
|
|
132
|
+
[2m Tests [22m [1m[32m86 passed[39m[22m[90m (86)[39m
|
|
133
|
+
[2m Start at [22m 17:47:43
|
|
134
|
+
[2m Duration [22m 1.88s[2m (transform 684ms, setup 1ms, collect 2.34s, tests 755ms, environment 2ms, prepare 1.27s)[22m
|
|
135
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`d1e1bfb`](https://github.com/cesr/poncho-ai/commit/d1e1bfbf35b18788ab79231ca675774e949f5116) Thanks [@cesr](https://github.com/cesr)! - Add proactive scheduled messaging via channel-targeted cron jobs. Cron jobs with `channel: telegram` (or `slack`) now automatically discover known conversations and send the agent's response directly to each chat, continuing the existing conversation history.
|
|
8
|
+
|
|
9
|
+
## 0.22.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`096953d`](https://github.com/cesr/poncho-ai/commit/096953d5a64a785950ea0a7f09e2183e481afd29) Thanks [@cesr](https://github.com/cesr)! - Improve time-to-first-token by lazy-loading the recall corpus
|
|
14
|
+
|
|
15
|
+
The recall corpus (past conversation summaries) is now fetched on-demand only when the LLM invokes the `conversation_recall` tool, instead of blocking every message with ~1.3s of upfront I/O. Also adds batch `mget` support to Upstash/Redis/DynamoDB conversation stores, parallelizes memory fetch with skill refresh, debounces skill refresh in dev mode, and caches message conversions across multi-step runs.
|
|
16
|
+
|
|
3
17
|
## 0.22.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface CronJobConfig {
|
|
|
19
19
|
schedule: string;
|
|
20
20
|
task: string;
|
|
21
21
|
timezone?: string;
|
|
22
|
+
channel?: string;
|
|
22
23
|
}
|
|
23
24
|
interface AgentFrontmatter {
|
|
24
25
|
name: string;
|
|
@@ -155,6 +156,11 @@ interface Conversation {
|
|
|
155
156
|
result?: _poncho_ai_sdk.RunResult;
|
|
156
157
|
error?: _poncho_ai_sdk.AgentFailure;
|
|
157
158
|
};
|
|
159
|
+
channelMeta?: {
|
|
160
|
+
platform: string;
|
|
161
|
+
channelId: string;
|
|
162
|
+
platformThreadId: string;
|
|
163
|
+
};
|
|
158
164
|
createdAt: number;
|
|
159
165
|
updatedAt: number;
|
|
160
166
|
}
|
|
@@ -208,6 +214,11 @@ type ConversationSummary = {
|
|
|
208
214
|
parentConversationId?: string;
|
|
209
215
|
messageCount?: number;
|
|
210
216
|
hasPendingApprovals?: boolean;
|
|
217
|
+
channelMeta?: {
|
|
218
|
+
platform: string;
|
|
219
|
+
channelId: string;
|
|
220
|
+
platformThreadId: string;
|
|
221
|
+
};
|
|
211
222
|
};
|
|
212
223
|
declare const createStateStore: (config?: StateConfig, options?: {
|
|
213
224
|
workingDir?: string;
|
|
@@ -562,6 +573,7 @@ declare class AgentHarness {
|
|
|
562
573
|
private loadedConfig?;
|
|
563
574
|
private loadedSkills;
|
|
564
575
|
private skillFingerprint;
|
|
576
|
+
private lastSkillRefreshAt;
|
|
565
577
|
private readonly activeSkillNames;
|
|
566
578
|
private readonly registeredMcpToolNames;
|
|
567
579
|
private latitudeTelemetry?;
|
|
@@ -601,6 +613,7 @@ declare class AgentHarness {
|
|
|
601
613
|
private refreshMcpTools;
|
|
602
614
|
private buildSkillFingerprint;
|
|
603
615
|
private registerSkillTools;
|
|
616
|
+
private static readonly SKILL_REFRESH_DEBOUNCE_MS;
|
|
604
617
|
private refreshSkillsIfChanged;
|
|
605
618
|
initialize(): Promise<void>;
|
|
606
619
|
private buildBrowserStoragePersistence;
|
package/dist/index.js
CHANGED
|
@@ -112,10 +112,12 @@ var parseCronJobs = (value) => {
|
|
|
112
112
|
if (timezone) {
|
|
113
113
|
validateTimezone(timezone, path);
|
|
114
114
|
}
|
|
115
|
+
const channel = typeof jobValue.channel === "string" && jobValue.channel.trim() ? jobValue.channel.trim() : void 0;
|
|
115
116
|
jobs[jobName] = {
|
|
116
117
|
schedule: jobValue.schedule.trim(),
|
|
117
118
|
task: jobValue.task,
|
|
118
|
-
timezone
|
|
119
|
+
timezone,
|
|
120
|
+
channel
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
123
|
return jobs;
|
|
@@ -1030,6 +1032,22 @@ messaging: [
|
|
|
1030
1032
|
]
|
|
1031
1033
|
\`\`\`
|
|
1032
1034
|
|
|
1035
|
+
#### Proactive scheduled messages
|
|
1036
|
+
|
|
1037
|
+
You can have the agent proactively message Telegram chats on a cron schedule. Add \`channel: telegram\` to any cron job in your \`AGENT.md\` frontmatter:
|
|
1038
|
+
|
|
1039
|
+
\`\`\`yaml
|
|
1040
|
+
cron:
|
|
1041
|
+
daily-checkin:
|
|
1042
|
+
schedule: "0 9 * * *"
|
|
1043
|
+
task: "Check in with the user about their plans for today"
|
|
1044
|
+
channel: telegram
|
|
1045
|
+
\`\`\`
|
|
1046
|
+
|
|
1047
|
+
The system auto-discovers all Telegram chats the bot has interacted with and sends the agent's response to each one. No chat IDs need to be configured -- filtering is handled by \`allowedUserIds\` if set. The agent runs with the full conversation history for each chat, so it has context from prior interactions.
|
|
1048
|
+
|
|
1049
|
+
The bot must have received at least one message from a user before it can send proactive messages to that chat (Telegram API requirement).
|
|
1050
|
+
|
|
1033
1051
|
### Email (Resend)
|
|
1034
1052
|
|
|
1035
1053
|
#### 1. Set up Resend
|
|
@@ -2771,10 +2789,9 @@ var createMemoryTools = (store, options) => {
|
|
|
2771
2789
|
Math.min(5, typeof input.limit === "number" ? input.limit : 3)
|
|
2772
2790
|
);
|
|
2773
2791
|
const excludeConversationId = typeof input.excludeConversationId === "string" ? input.excludeConversationId : "";
|
|
2774
|
-
const
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
);
|
|
2792
|
+
const rawCorpus = context.parameters.__conversationRecallCorpus;
|
|
2793
|
+
const resolvedCorpus = typeof rawCorpus === "function" ? await rawCorpus() : rawCorpus;
|
|
2794
|
+
const corpus = asRecallCorpus(resolvedCorpus).slice(0, maxRecallConversations);
|
|
2778
2795
|
const results = corpus.filter(
|
|
2779
2796
|
(item) => excludeConversationId ? item.conversationId !== excludeConversationId : true
|
|
2780
2797
|
).map((item) => ({
|
|
@@ -4366,6 +4383,10 @@ cron:
|
|
|
4366
4383
|
schedule: "0 9 * * *" # Standard 5-field cron expression
|
|
4367
4384
|
timezone: "America/New_York" # Optional IANA timezone (default: UTC)
|
|
4368
4385
|
task: "Generate the daily sales report"
|
|
4386
|
+
telegram-checkin:
|
|
4387
|
+
schedule: "0 18 * * 1-5"
|
|
4388
|
+
channel: telegram # Proactive message to all known Telegram chats
|
|
4389
|
+
task: "Send an end-of-day summary to the user"
|
|
4369
4390
|
\`\`\`
|
|
4370
4391
|
|
|
4371
4392
|
- Each cron job triggers an autonomous agent run with the specified task, creating a fresh conversation.
|
|
@@ -4374,6 +4395,7 @@ cron:
|
|
|
4374
4395
|
- Jobs can also be triggered manually: \`GET /api/cron/<jobName>\`.
|
|
4375
4396
|
- To carry context across cron runs, enable memory.
|
|
4376
4397
|
- **IMPORTANT**: When adding a new cron job, always PRESERVE all existing cron jobs. Never remove or overwrite existing jobs unless the user explicitly asks you to replace or delete them. Read the full current \`cron:\` block before editing, and append the new job alongside the existing ones.
|
|
4398
|
+
- **Proactive channel messaging**: Adding \`channel: telegram\` (or \`slack\`) makes the cron job send its response directly to all known conversations on that platform, instead of creating a standalone conversation. The agent continues the existing conversation history for context. A chat must have at least one prior user message for auto-discovery to find it.
|
|
4377
4399
|
|
|
4378
4400
|
## Messaging Integrations (Slack, Telegram, Email)
|
|
4379
4401
|
|
|
@@ -4599,7 +4621,7 @@ function extractMediaFromToolOutput(output) {
|
|
|
4599
4621
|
const strippedOutput = walk(output);
|
|
4600
4622
|
return { mediaItems, strippedOutput };
|
|
4601
4623
|
}
|
|
4602
|
-
var AgentHarness = class {
|
|
4624
|
+
var AgentHarness = class _AgentHarness {
|
|
4603
4625
|
workingDir;
|
|
4604
4626
|
environment;
|
|
4605
4627
|
modelProvider;
|
|
@@ -4611,6 +4633,7 @@ var AgentHarness = class {
|
|
|
4611
4633
|
loadedConfig;
|
|
4612
4634
|
loadedSkills = [];
|
|
4613
4635
|
skillFingerprint = "";
|
|
4636
|
+
lastSkillRefreshAt = 0;
|
|
4614
4637
|
activeSkillNames = /* @__PURE__ */ new Set();
|
|
4615
4638
|
registeredMcpToolNames = /* @__PURE__ */ new Set();
|
|
4616
4639
|
latitudeTelemetry;
|
|
@@ -4899,10 +4922,16 @@ var AgentHarness = class {
|
|
|
4899
4922
|
})
|
|
4900
4923
|
);
|
|
4901
4924
|
}
|
|
4925
|
+
static SKILL_REFRESH_DEBOUNCE_MS = 3e3;
|
|
4902
4926
|
async refreshSkillsIfChanged() {
|
|
4903
4927
|
if (this.environment !== "development") {
|
|
4904
4928
|
return;
|
|
4905
4929
|
}
|
|
4930
|
+
const elapsed = Date.now() - this.lastSkillRefreshAt;
|
|
4931
|
+
if (this.lastSkillRefreshAt > 0 && elapsed < _AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
|
|
4932
|
+
return;
|
|
4933
|
+
}
|
|
4934
|
+
this.lastSkillRefreshAt = Date.now();
|
|
4906
4935
|
try {
|
|
4907
4936
|
const latestSkills = await loadSkillMetadata(
|
|
4908
4937
|
this.workingDir,
|
|
@@ -5228,6 +5257,7 @@ var AgentHarness = class {
|
|
|
5228
5257
|
if (!this.parsedAgent) {
|
|
5229
5258
|
await this.initialize();
|
|
5230
5259
|
}
|
|
5260
|
+
const memoryPromise = this.memoryStore ? this.memoryStore.getMainMemory() : void 0;
|
|
5231
5261
|
await this.refreshSkillsIfChanged();
|
|
5232
5262
|
this._currentRunConversationId = input.conversationId;
|
|
5233
5263
|
const ownerParam = input.parameters?.__ownerId;
|
|
@@ -5280,7 +5310,7 @@ Each conversation gets its own browser tab sharing a single browser instance. Ca
|
|
|
5280
5310
|
const promptWithSkills = this.skillContextWindow ? `${systemPrompt}${developmentContext}
|
|
5281
5311
|
|
|
5282
5312
|
${this.skillContextWindow}${browserContext}` : `${systemPrompt}${developmentContext}${browserContext}`;
|
|
5283
|
-
const mainMemory =
|
|
5313
|
+
const mainMemory = await memoryPromise;
|
|
5284
5314
|
const boundedMainMemory = mainMemory && mainMemory.content.length > 4e3 ? `${mainMemory.content.slice(0, 4e3)}
|
|
5285
5315
|
...[truncated]` : mainMemory?.content;
|
|
5286
5316
|
const memoryContext = boundedMainMemory && boundedMainMemory.trim().length > 0 ? `
|
|
@@ -5375,6 +5405,8 @@ ${boundedMainMemory.trim()}` : "";
|
|
|
5375
5405
|
let totalOutputTokens = 0;
|
|
5376
5406
|
let totalCachedTokens = 0;
|
|
5377
5407
|
let transientStepRetryCount = 0;
|
|
5408
|
+
let cachedCoreMessages = [];
|
|
5409
|
+
let convertedUpTo = 0;
|
|
5378
5410
|
for (let step = 1; step <= maxSteps; step += 1) {
|
|
5379
5411
|
try {
|
|
5380
5412
|
yield* drainBrowserEvents();
|
|
@@ -5651,7 +5683,15 @@ ${textContent}` };
|
|
|
5651
5683
|
}
|
|
5652
5684
|
}
|
|
5653
5685
|
}
|
|
5654
|
-
|
|
5686
|
+
if (convertedUpTo > messages.length) {
|
|
5687
|
+
cachedCoreMessages = [];
|
|
5688
|
+
convertedUpTo = 0;
|
|
5689
|
+
}
|
|
5690
|
+
const newMessages = messages.slice(convertedUpTo);
|
|
5691
|
+
const newCoreMessages = newMessages.length > 0 ? (await Promise.all(newMessages.map(convertMessage))).flat() : [];
|
|
5692
|
+
cachedCoreMessages = [...cachedCoreMessages, ...newCoreMessages];
|
|
5693
|
+
convertedUpTo = messages.length;
|
|
5694
|
+
const coreMessages = cachedCoreMessages;
|
|
5655
5695
|
const temperature = agent.frontmatter.model?.temperature ?? 0.2;
|
|
5656
5696
|
const maxTokens = agent.frontmatter.model?.maxTokens;
|
|
5657
5697
|
const cachedMessages = addPromptCacheBreakpoints(coreMessages, modelInstance);
|
|
@@ -6333,7 +6373,8 @@ var InMemoryConversationStore = class {
|
|
|
6333
6373
|
ownerId: c.ownerId,
|
|
6334
6374
|
parentConversationId: c.parentConversationId,
|
|
6335
6375
|
messageCount: c.messages.length,
|
|
6336
|
-
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0
|
|
6376
|
+
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
6377
|
+
channelMeta: c.channelMeta
|
|
6337
6378
|
}));
|
|
6338
6379
|
}
|
|
6339
6380
|
async get(conversationId) {
|
|
@@ -6495,7 +6536,8 @@ var FileConversationStore = class {
|
|
|
6495
6536
|
fileName,
|
|
6496
6537
|
parentConversationId: conversation.parentConversationId,
|
|
6497
6538
|
messageCount: conversation.messages.length,
|
|
6498
|
-
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0
|
|
6539
|
+
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0,
|
|
6540
|
+
channelMeta: conversation.channelMeta
|
|
6499
6541
|
});
|
|
6500
6542
|
await this.writeIndex();
|
|
6501
6543
|
});
|
|
@@ -6523,7 +6565,8 @@ var FileConversationStore = class {
|
|
|
6523
6565
|
ownerId: c.ownerId,
|
|
6524
6566
|
parentConversationId: c.parentConversationId,
|
|
6525
6567
|
messageCount: c.messageCount,
|
|
6526
|
-
hasPendingApprovals: c.hasPendingApprovals
|
|
6568
|
+
hasPendingApprovals: c.hasPendingApprovals,
|
|
6569
|
+
channelMeta: c.channelMeta
|
|
6527
6570
|
}));
|
|
6528
6571
|
}
|
|
6529
6572
|
async get(conversationId) {
|
|
@@ -6750,12 +6793,12 @@ var KeyValueConversationStoreBase = class {
|
|
|
6750
6793
|
return [];
|
|
6751
6794
|
}
|
|
6752
6795
|
const ids = await this.getOwnerConversationIds(ownerId);
|
|
6796
|
+
if (ids.length === 0) return [];
|
|
6797
|
+
const convKeys = await Promise.all(ids.map((id) => this.conversationKey(id)));
|
|
6798
|
+
const rawValues = await kv.mget(convKeys);
|
|
6753
6799
|
const conversations = [];
|
|
6754
|
-
for (const
|
|
6755
|
-
|
|
6756
|
-
if (!raw) {
|
|
6757
|
-
continue;
|
|
6758
|
-
}
|
|
6800
|
+
for (const raw of rawValues) {
|
|
6801
|
+
if (!raw) continue;
|
|
6759
6802
|
try {
|
|
6760
6803
|
conversations.push(JSON.parse(raw));
|
|
6761
6804
|
} catch {
|
|
@@ -6772,20 +6815,28 @@ var KeyValueConversationStoreBase = class {
|
|
|
6772
6815
|
return [];
|
|
6773
6816
|
}
|
|
6774
6817
|
const ids = await this.getOwnerConversationIds(ownerId);
|
|
6818
|
+
if (ids.length === 0) return [];
|
|
6819
|
+
const metaKeys = await Promise.all(ids.map((id) => this.conversationMetaKey(id)));
|
|
6820
|
+
const rawValues = await kv.mget(metaKeys);
|
|
6775
6821
|
const summaries = [];
|
|
6776
|
-
for (const
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6822
|
+
for (const raw of rawValues) {
|
|
6823
|
+
if (!raw) continue;
|
|
6824
|
+
try {
|
|
6825
|
+
const meta = JSON.parse(raw);
|
|
6826
|
+
if (meta.ownerId === ownerId) {
|
|
6827
|
+
summaries.push({
|
|
6828
|
+
conversationId: meta.conversationId,
|
|
6829
|
+
title: meta.title,
|
|
6830
|
+
updatedAt: meta.updatedAt,
|
|
6831
|
+
createdAt: meta.createdAt,
|
|
6832
|
+
ownerId: meta.ownerId,
|
|
6833
|
+
parentConversationId: meta.parentConversationId,
|
|
6834
|
+
messageCount: meta.messageCount,
|
|
6835
|
+
hasPendingApprovals: meta.hasPendingApprovals,
|
|
6836
|
+
channelMeta: meta.channelMeta
|
|
6837
|
+
});
|
|
6838
|
+
}
|
|
6839
|
+
} catch {
|
|
6789
6840
|
}
|
|
6790
6841
|
}
|
|
6791
6842
|
return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
@@ -6843,7 +6894,8 @@ var KeyValueConversationStoreBase = class {
|
|
|
6843
6894
|
ownerId: nextConversation.ownerId,
|
|
6844
6895
|
parentConversationId: nextConversation.parentConversationId,
|
|
6845
6896
|
messageCount: nextConversation.messages.length,
|
|
6846
|
-
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0
|
|
6897
|
+
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0,
|
|
6898
|
+
channelMeta: nextConversation.channelMeta
|
|
6847
6899
|
}),
|
|
6848
6900
|
this.ttl
|
|
6849
6901
|
);
|
|
@@ -6923,6 +6975,19 @@ var UpstashConversationStore = class extends KeyValueConversationStoreBase {
|
|
|
6923
6975
|
const payload = await response.json();
|
|
6924
6976
|
return payload.result ?? void 0;
|
|
6925
6977
|
},
|
|
6978
|
+
mget: async (keys) => {
|
|
6979
|
+
if (keys.length === 0) return [];
|
|
6980
|
+
const path = keys.map((k) => encodeURIComponent(k)).join("/");
|
|
6981
|
+
const response = await fetch(`${this.baseUrl}/mget/${path}`, {
|
|
6982
|
+
method: "POST",
|
|
6983
|
+
headers: this.headers()
|
|
6984
|
+
});
|
|
6985
|
+
if (!response.ok) {
|
|
6986
|
+
return keys.map(() => void 0);
|
|
6987
|
+
}
|
|
6988
|
+
const payload = await response.json();
|
|
6989
|
+
return (payload.result ?? []).map((v) => v ?? void 0);
|
|
6990
|
+
},
|
|
6926
6991
|
set: async (key, value, ttl) => {
|
|
6927
6992
|
const endpoint = typeof ttl === "number" ? `${this.baseUrl}/setex/${encodeURIComponent(key)}/${Math.max(
|
|
6928
6993
|
1,
|
|
@@ -7015,6 +7080,11 @@ var RedisLikeConversationStore = class extends KeyValueConversationStoreBase {
|
|
|
7015
7080
|
const value = await client.get(key);
|
|
7016
7081
|
return value ?? void 0;
|
|
7017
7082
|
},
|
|
7083
|
+
mget: async (keys) => {
|
|
7084
|
+
if (keys.length === 0) return [];
|
|
7085
|
+
const values = await client.mGet(keys);
|
|
7086
|
+
return values.map((v) => v ?? void 0);
|
|
7087
|
+
},
|
|
7018
7088
|
set: async (key, value, ttl) => {
|
|
7019
7089
|
if (typeof ttl === "number") {
|
|
7020
7090
|
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
@@ -7149,6 +7219,18 @@ var DynamoDbConversationStore = class extends KeyValueConversationStoreBase {
|
|
|
7149
7219
|
})
|
|
7150
7220
|
);
|
|
7151
7221
|
},
|
|
7222
|
+
mget: async (keys) => {
|
|
7223
|
+
if (keys.length === 0) return [];
|
|
7224
|
+
return Promise.all(keys.map(async (key) => {
|
|
7225
|
+
const result = await client.send(
|
|
7226
|
+
new client.GetItemCommand({
|
|
7227
|
+
TableName: this.table,
|
|
7228
|
+
Key: { runId: { S: key } }
|
|
7229
|
+
})
|
|
7230
|
+
);
|
|
7231
|
+
return result.Item?.value?.S;
|
|
7232
|
+
}));
|
|
7233
|
+
},
|
|
7152
7234
|
del: async (key) => {
|
|
7153
7235
|
await client.send(
|
|
7154
7236
|
new client.DeleteItemCommand({
|
package/package.json
CHANGED
package/src/agent-parser.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface CronJobConfig {
|
|
|
27
27
|
schedule: string;
|
|
28
28
|
task: string;
|
|
29
29
|
timezone?: string;
|
|
30
|
+
channel?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export interface AgentFrontmatter {
|
|
@@ -138,10 +139,16 @@ const parseCronJobs = (
|
|
|
138
139
|
validateTimezone(timezone, path);
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
const channel =
|
|
143
|
+
typeof jobValue.channel === "string" && jobValue.channel.trim()
|
|
144
|
+
? jobValue.channel.trim()
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
141
147
|
jobs[jobName] = {
|
|
142
148
|
schedule: jobValue.schedule.trim(),
|
|
143
149
|
task: jobValue.task,
|
|
144
150
|
timezone,
|
|
151
|
+
channel,
|
|
145
152
|
};
|
|
146
153
|
}
|
|
147
154
|
return jobs;
|
package/src/harness.ts
CHANGED
|
@@ -280,6 +280,10 @@ cron:
|
|
|
280
280
|
schedule: "0 9 * * *" # Standard 5-field cron expression
|
|
281
281
|
timezone: "America/New_York" # Optional IANA timezone (default: UTC)
|
|
282
282
|
task: "Generate the daily sales report"
|
|
283
|
+
telegram-checkin:
|
|
284
|
+
schedule: "0 18 * * 1-5"
|
|
285
|
+
channel: telegram # Proactive message to all known Telegram chats
|
|
286
|
+
task: "Send an end-of-day summary to the user"
|
|
283
287
|
\`\`\`
|
|
284
288
|
|
|
285
289
|
- Each cron job triggers an autonomous agent run with the specified task, creating a fresh conversation.
|
|
@@ -288,6 +292,7 @@ cron:
|
|
|
288
292
|
- Jobs can also be triggered manually: \`GET /api/cron/<jobName>\`.
|
|
289
293
|
- To carry context across cron runs, enable memory.
|
|
290
294
|
- **IMPORTANT**: When adding a new cron job, always PRESERVE all existing cron jobs. Never remove or overwrite existing jobs unless the user explicitly asks you to replace or delete them. Read the full current \`cron:\` block before editing, and append the new job alongside the existing ones.
|
|
295
|
+
- **Proactive channel messaging**: Adding \`channel: telegram\` (or \`slack\`) makes the cron job send its response directly to all known conversations on that platform, instead of creating a standalone conversation. The agent continues the existing conversation history for context. A chat must have at least one prior user message for auto-discovery to find it.
|
|
291
296
|
|
|
292
297
|
## Messaging Integrations (Slack, Telegram, Email)
|
|
293
298
|
|
|
@@ -545,6 +550,7 @@ export class AgentHarness {
|
|
|
545
550
|
private loadedConfig?: PonchoConfig;
|
|
546
551
|
private loadedSkills: SkillMetadata[] = [];
|
|
547
552
|
private skillFingerprint = "";
|
|
553
|
+
private lastSkillRefreshAt = 0;
|
|
548
554
|
private readonly activeSkillNames = new Set<string>();
|
|
549
555
|
private readonly registeredMcpToolNames = new Set<string>();
|
|
550
556
|
private latitudeTelemetry?: LatitudeTelemetry;
|
|
@@ -879,10 +885,17 @@ export class AgentHarness {
|
|
|
879
885
|
);
|
|
880
886
|
}
|
|
881
887
|
|
|
888
|
+
private static readonly SKILL_REFRESH_DEBOUNCE_MS = 3000;
|
|
889
|
+
|
|
882
890
|
private async refreshSkillsIfChanged(): Promise<void> {
|
|
883
891
|
if (this.environment !== "development") {
|
|
884
892
|
return;
|
|
885
893
|
}
|
|
894
|
+
const elapsed = Date.now() - this.lastSkillRefreshAt;
|
|
895
|
+
if (this.lastSkillRefreshAt > 0 && elapsed < AgentHarness.SKILL_REFRESH_DEBOUNCE_MS) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
this.lastSkillRefreshAt = Date.now();
|
|
886
899
|
try {
|
|
887
900
|
const latestSkills = await loadSkillMetadata(
|
|
888
901
|
this.workingDir,
|
|
@@ -1269,6 +1282,10 @@ export class AgentHarness {
|
|
|
1269
1282
|
if (!this.parsedAgent) {
|
|
1270
1283
|
await this.initialize();
|
|
1271
1284
|
}
|
|
1285
|
+
// Start memory fetch early so it overlaps with skill refresh I/O
|
|
1286
|
+
const memoryPromise = this.memoryStore
|
|
1287
|
+
? this.memoryStore.getMainMemory()
|
|
1288
|
+
: undefined;
|
|
1272
1289
|
await this.refreshSkillsIfChanged();
|
|
1273
1290
|
|
|
1274
1291
|
// Track which conversation/owner this run belongs to so browser & subagent tools resolve correctly
|
|
@@ -1328,9 +1345,7 @@ Each conversation gets its own browser tab sharing a single browser instance. Ca
|
|
|
1328
1345
|
const promptWithSkills = this.skillContextWindow
|
|
1329
1346
|
? `${systemPrompt}${developmentContext}\n\n${this.skillContextWindow}${browserContext}`
|
|
1330
1347
|
: `${systemPrompt}${developmentContext}${browserContext}`;
|
|
1331
|
-
const mainMemory =
|
|
1332
|
-
? await this.memoryStore.getMainMemory()
|
|
1333
|
-
: undefined;
|
|
1348
|
+
const mainMemory = await memoryPromise;
|
|
1334
1349
|
const boundedMainMemory =
|
|
1335
1350
|
mainMemory && mainMemory.content.length > 4000
|
|
1336
1351
|
? `${mainMemory.content.slice(0, 4000)}\n...[truncated]`
|
|
@@ -1445,6 +1460,8 @@ ${boundedMainMemory.trim()}`
|
|
|
1445
1460
|
let totalOutputTokens = 0;
|
|
1446
1461
|
let totalCachedTokens = 0;
|
|
1447
1462
|
let transientStepRetryCount = 0;
|
|
1463
|
+
let cachedCoreMessages: ModelMessage[] = [];
|
|
1464
|
+
let convertedUpTo = 0;
|
|
1448
1465
|
|
|
1449
1466
|
for (let step = 1; step <= maxSteps; step += 1) {
|
|
1450
1467
|
try {
|
|
@@ -1784,9 +1801,19 @@ ${boundedMainMemory.trim()}`
|
|
|
1784
1801
|
}
|
|
1785
1802
|
}
|
|
1786
1803
|
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1804
|
+
// Only convert messages added since the last step
|
|
1805
|
+
if (convertedUpTo > messages.length) {
|
|
1806
|
+
// Compaction replaced the array — invalidate cache
|
|
1807
|
+
cachedCoreMessages = [];
|
|
1808
|
+
convertedUpTo = 0;
|
|
1809
|
+
}
|
|
1810
|
+
const newMessages = messages.slice(convertedUpTo);
|
|
1811
|
+
const newCoreMessages: ModelMessage[] = newMessages.length > 0
|
|
1812
|
+
? (await Promise.all(newMessages.map(convertMessage))).flat()
|
|
1813
|
+
: [];
|
|
1814
|
+
cachedCoreMessages = [...cachedCoreMessages, ...newCoreMessages];
|
|
1815
|
+
convertedUpTo = messages.length;
|
|
1816
|
+
const coreMessages = cachedCoreMessages;
|
|
1790
1817
|
|
|
1791
1818
|
const temperature = agent.frontmatter.model?.temperature ?? 0.2;
|
|
1792
1819
|
const maxTokens = agent.frontmatter.model?.maxTokens;
|
|
@@ -1794,6 +1821,7 @@ ${boundedMainMemory.trim()}`
|
|
|
1794
1821
|
|
|
1795
1822
|
const telemetryEnabled = this.loadedConfig?.telemetry?.enabled !== false;
|
|
1796
1823
|
|
|
1824
|
+
|
|
1797
1825
|
const result = await streamText({
|
|
1798
1826
|
model: modelInstance,
|
|
1799
1827
|
system: integrityPrompt,
|
package/src/memory.ts
CHANGED
|
@@ -658,10 +658,10 @@ export const createMemoryTools = (
|
|
|
658
658
|
typeof input.excludeConversationId === "string"
|
|
659
659
|
? input.excludeConversationId
|
|
660
660
|
: "";
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
);
|
|
661
|
+
const rawCorpus = context.parameters.__conversationRecallCorpus;
|
|
662
|
+
const resolvedCorpus =
|
|
663
|
+
typeof rawCorpus === "function" ? await (rawCorpus as () => Promise<unknown>)() : rawCorpus;
|
|
664
|
+
const corpus = asRecallCorpus(resolvedCorpus).slice(0, maxRecallConversations);
|
|
665
665
|
const results = corpus
|
|
666
666
|
.filter((item) =>
|
|
667
667
|
excludeConversationId ? item.conversationId !== excludeConversationId : true,
|
package/src/state.ts
CHANGED
|
@@ -50,6 +50,11 @@ export interface Conversation {
|
|
|
50
50
|
result?: import("@poncho-ai/sdk").RunResult;
|
|
51
51
|
error?: import("@poncho-ai/sdk").AgentFailure;
|
|
52
52
|
};
|
|
53
|
+
channelMeta?: {
|
|
54
|
+
platform: string;
|
|
55
|
+
channelId: string;
|
|
56
|
+
platformThreadId: string;
|
|
57
|
+
};
|
|
53
58
|
createdAt: number;
|
|
54
59
|
updatedAt: number;
|
|
55
60
|
}
|
|
@@ -247,6 +252,7 @@ export class InMemoryConversationStore implements ConversationStore {
|
|
|
247
252
|
parentConversationId: c.parentConversationId,
|
|
248
253
|
messageCount: c.messages.length,
|
|
249
254
|
hasPendingApprovals: Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
255
|
+
channelMeta: c.channelMeta,
|
|
250
256
|
}));
|
|
251
257
|
}
|
|
252
258
|
|
|
@@ -305,6 +311,11 @@ export type ConversationSummary = {
|
|
|
305
311
|
parentConversationId?: string;
|
|
306
312
|
messageCount?: number;
|
|
307
313
|
hasPendingApprovals?: boolean;
|
|
314
|
+
channelMeta?: {
|
|
315
|
+
platform: string;
|
|
316
|
+
channelId: string;
|
|
317
|
+
platformThreadId: string;
|
|
318
|
+
};
|
|
308
319
|
};
|
|
309
320
|
|
|
310
321
|
type ConversationStoreFile = {
|
|
@@ -319,6 +330,11 @@ type ConversationStoreFile = {
|
|
|
319
330
|
parentConversationId?: string;
|
|
320
331
|
messageCount?: number;
|
|
321
332
|
hasPendingApprovals?: boolean;
|
|
333
|
+
channelMeta?: {
|
|
334
|
+
platform: string;
|
|
335
|
+
channelId: string;
|
|
336
|
+
platformThreadId: string;
|
|
337
|
+
};
|
|
322
338
|
}>;
|
|
323
339
|
};
|
|
324
340
|
|
|
@@ -451,6 +467,7 @@ class FileConversationStore implements ConversationStore {
|
|
|
451
467
|
parentConversationId: conversation.parentConversationId,
|
|
452
468
|
messageCount: conversation.messages.length,
|
|
453
469
|
hasPendingApprovals: Array.isArray(conversation.pendingApprovals) && conversation.pendingApprovals.length > 0,
|
|
470
|
+
channelMeta: conversation.channelMeta,
|
|
454
471
|
});
|
|
455
472
|
await this.writeIndex();
|
|
456
473
|
});
|
|
@@ -486,6 +503,7 @@ class FileConversationStore implements ConversationStore {
|
|
|
486
503
|
parentConversationId: c.parentConversationId,
|
|
487
504
|
messageCount: c.messageCount,
|
|
488
505
|
hasPendingApprovals: c.hasPendingApprovals,
|
|
506
|
+
channelMeta: c.channelMeta,
|
|
489
507
|
}));
|
|
490
508
|
}
|
|
491
509
|
|
|
@@ -646,6 +664,7 @@ class FileStateStore implements StateStore {
|
|
|
646
664
|
|
|
647
665
|
interface RawKeyValueClient {
|
|
648
666
|
get(key: string): Promise<string | undefined>;
|
|
667
|
+
mget(keys: string[]): Promise<(string | undefined)[]>;
|
|
649
668
|
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
650
669
|
del(key: string): Promise<void>;
|
|
651
670
|
}
|
|
@@ -659,6 +678,11 @@ type ConversationMeta = {
|
|
|
659
678
|
parentConversationId?: string;
|
|
660
679
|
messageCount?: number;
|
|
661
680
|
hasPendingApprovals?: boolean;
|
|
681
|
+
channelMeta?: {
|
|
682
|
+
platform: string;
|
|
683
|
+
channelId: string;
|
|
684
|
+
platformThreadId: string;
|
|
685
|
+
};
|
|
662
686
|
};
|
|
663
687
|
|
|
664
688
|
abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
@@ -760,21 +784,18 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
|
760
784
|
return await this.memoryFallback.list(ownerId);
|
|
761
785
|
}
|
|
762
786
|
if (!ownerId) {
|
|
763
|
-
// KV stores index per-owner; cross-owner listing not supported
|
|
764
787
|
return [];
|
|
765
788
|
}
|
|
766
789
|
const ids = await this.getOwnerConversationIds(ownerId);
|
|
790
|
+
if (ids.length === 0) return [];
|
|
791
|
+
const convKeys = await Promise.all(ids.map((id) => this.conversationKey(id)));
|
|
792
|
+
const rawValues = await kv.mget(convKeys);
|
|
767
793
|
const conversations: Conversation[] = [];
|
|
768
|
-
for (const
|
|
769
|
-
|
|
770
|
-
if (!raw) {
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
794
|
+
for (const raw of rawValues) {
|
|
795
|
+
if (!raw) continue;
|
|
773
796
|
try {
|
|
774
797
|
conversations.push(JSON.parse(raw) as Conversation);
|
|
775
|
-
} catch {
|
|
776
|
-
// Skip invalid records.
|
|
777
|
-
}
|
|
798
|
+
} catch { /* skip invalid records */ }
|
|
778
799
|
}
|
|
779
800
|
return conversations.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
780
801
|
}
|
|
@@ -788,21 +809,28 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
|
788
809
|
return [];
|
|
789
810
|
}
|
|
790
811
|
const ids = await this.getOwnerConversationIds(ownerId);
|
|
812
|
+
if (ids.length === 0) return [];
|
|
813
|
+
const metaKeys = await Promise.all(ids.map((id) => this.conversationMetaKey(id)));
|
|
814
|
+
const rawValues = await kv.mget(metaKeys);
|
|
791
815
|
const summaries: ConversationSummary[] = [];
|
|
792
|
-
for (const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
816
|
+
for (const raw of rawValues) {
|
|
817
|
+
if (!raw) continue;
|
|
818
|
+
try {
|
|
819
|
+
const meta = JSON.parse(raw) as ConversationMeta;
|
|
820
|
+
if (meta.ownerId === ownerId) {
|
|
821
|
+
summaries.push({
|
|
822
|
+
conversationId: meta.conversationId,
|
|
823
|
+
title: meta.title,
|
|
824
|
+
updatedAt: meta.updatedAt,
|
|
825
|
+
createdAt: meta.createdAt,
|
|
826
|
+
ownerId: meta.ownerId,
|
|
827
|
+
parentConversationId: meta.parentConversationId,
|
|
828
|
+
messageCount: meta.messageCount,
|
|
829
|
+
hasPendingApprovals: meta.hasPendingApprovals,
|
|
830
|
+
channelMeta: meta.channelMeta,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
} catch { /* skip invalid records */ }
|
|
806
834
|
}
|
|
807
835
|
return summaries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
808
836
|
}
|
|
@@ -863,6 +891,7 @@ abstract class KeyValueConversationStoreBase implements ConversationStore {
|
|
|
863
891
|
parentConversationId: nextConversation.parentConversationId,
|
|
864
892
|
messageCount: nextConversation.messages.length,
|
|
865
893
|
hasPendingApprovals: Array.isArray(nextConversation.pendingApprovals) && nextConversation.pendingApprovals.length > 0,
|
|
894
|
+
channelMeta: nextConversation.channelMeta,
|
|
866
895
|
} satisfies ConversationMeta),
|
|
867
896
|
this.ttl,
|
|
868
897
|
);
|
|
@@ -948,6 +977,19 @@ class UpstashConversationStore extends KeyValueConversationStoreBase {
|
|
|
948
977
|
const payload = (await response.json()) as { result?: string | null };
|
|
949
978
|
return payload.result ?? undefined;
|
|
950
979
|
},
|
|
980
|
+
mget: async (keys: string[]) => {
|
|
981
|
+
if (keys.length === 0) return [];
|
|
982
|
+
const path = keys.map((k) => encodeURIComponent(k)).join("/");
|
|
983
|
+
const response = await fetch(`${this.baseUrl}/mget/${path}`, {
|
|
984
|
+
method: "POST",
|
|
985
|
+
headers: this.headers(),
|
|
986
|
+
});
|
|
987
|
+
if (!response.ok) {
|
|
988
|
+
return keys.map(() => undefined);
|
|
989
|
+
}
|
|
990
|
+
const payload = (await response.json()) as { result?: (string | null)[] };
|
|
991
|
+
return (payload.result ?? []).map((v) => v ?? undefined);
|
|
992
|
+
},
|
|
951
993
|
set: async (key: string, value: string, ttl?: number) => {
|
|
952
994
|
const endpoint =
|
|
953
995
|
typeof ttl === "number"
|
|
@@ -1042,6 +1084,7 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
|
|
|
1042
1084
|
private readonly clientPromise: Promise<
|
|
1043
1085
|
| {
|
|
1044
1086
|
get: (key: string) => Promise<string | null>;
|
|
1087
|
+
mGet: (keys: readonly string[]) => Promise<(string | null)[]>;
|
|
1045
1088
|
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
1046
1089
|
del: (key: string) => Promise<unknown>;
|
|
1047
1090
|
}
|
|
@@ -1056,6 +1099,7 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
|
|
|
1056
1099
|
createClient: (options: { url: string }) => {
|
|
1057
1100
|
connect: () => Promise<unknown>;
|
|
1058
1101
|
get: (key: string) => Promise<string | null>;
|
|
1102
|
+
mGet: (keys: readonly string[]) => Promise<(string | null)[]>;
|
|
1059
1103
|
set: (key: string, value: string, options?: { EX?: number }) => Promise<unknown>;
|
|
1060
1104
|
del: (key: string) => Promise<unknown>;
|
|
1061
1105
|
};
|
|
@@ -1079,6 +1123,11 @@ class RedisLikeConversationStore extends KeyValueConversationStoreBase {
|
|
|
1079
1123
|
const value = await client.get(key);
|
|
1080
1124
|
return value ?? undefined;
|
|
1081
1125
|
},
|
|
1126
|
+
mget: async (keys: string[]) => {
|
|
1127
|
+
if (keys.length === 0) return [];
|
|
1128
|
+
const values = await client.mGet(keys);
|
|
1129
|
+
return values.map((v: string | null) => v ?? undefined);
|
|
1130
|
+
},
|
|
1082
1131
|
set: async (key: string, value: string, ttl?: number) => {
|
|
1083
1132
|
if (typeof ttl === "number") {
|
|
1084
1133
|
await client.set(key, value, { EX: Math.max(1, ttl) });
|
|
@@ -1261,6 +1310,18 @@ class DynamoDbConversationStore extends KeyValueConversationStoreBase {
|
|
|
1261
1310
|
}),
|
|
1262
1311
|
);
|
|
1263
1312
|
},
|
|
1313
|
+
mget: async (keys: string[]) => {
|
|
1314
|
+
if (keys.length === 0) return [];
|
|
1315
|
+
return Promise.all(keys.map(async (key) => {
|
|
1316
|
+
const result = (await client.send(
|
|
1317
|
+
new client.GetItemCommand({
|
|
1318
|
+
TableName: this.table,
|
|
1319
|
+
Key: { runId: { S: key } },
|
|
1320
|
+
}),
|
|
1321
|
+
)) as { Item?: { value?: { S?: string } } };
|
|
1322
|
+
return result.Item?.value?.S;
|
|
1323
|
+
}));
|
|
1324
|
+
},
|
|
1264
1325
|
del: async (key: string) => {
|
|
1265
1326
|
await client.send(
|
|
1266
1327
|
new client.DeleteItemCommand({
|