fuli-context 0.9.0-beta.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -43
- package/README.zh-CN.md +14 -40
- package/dist/web/assets/AboutPage-D-GyJNv1.js +1 -0
- package/dist/web/assets/AboutPage-c4dQpUiP.css +1 -0
- package/dist/web/assets/AgentConversations-DOuZgUeA.js +2 -0
- package/dist/web/assets/AgentConversations-DS8h3yKn.css +1 -0
- package/dist/web/assets/AgentHand-Y83Oo36U.css +1 -0
- package/dist/web/assets/AgentHand-eGsXoGSq.js +1 -0
- package/dist/web/assets/AgentName-BQTFCWn0.css +1 -0
- package/dist/web/assets/AgentName-Dtmu9Ff5.js +1 -0
- package/dist/web/assets/AgentProfilePage-D8Tvayn7.js +1 -0
- package/dist/web/assets/AgentsDirectoryPage-BTEfKZjF.js +1 -0
- package/dist/web/assets/ConnectionsPage-C1w99yN3.css +1 -0
- package/dist/web/assets/ConnectionsPage-DwOIcvl9.js +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-BrIMRblq.css +1 -0
- package/dist/web/assets/EmployeeRecruitDialog-D11blfwU.js +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-BcEzYt65.css +1 -0
- package/dist/web/assets/EmployeeWorkbenchPage-CK2BAoGt.js +3 -0
- package/dist/web/assets/GrowthLoading-CIuQ1icy.css +1 -0
- package/dist/web/assets/GrowthLoading-KTjE2e50.js +1 -0
- package/dist/web/assets/KnowledgeEditDialog-B3x8vs3c.css +1 -0
- package/dist/web/assets/KnowledgeEditDialog-n9trHQ5d.js +1 -0
- package/dist/web/assets/KnowledgeInspector-BcNjgw9H.js +1 -0
- package/dist/web/assets/KnowledgeInspector-pgDELOEf.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-BzgxP-nG.css +1 -0
- package/dist/web/assets/KnowledgeOrganizerPage-DMBpGxLr.js +1 -0
- package/dist/web/assets/KnowledgePage-POkCMW8f.js +1 -0
- package/dist/web/assets/KnowledgeWorkspace-2FvQadhB.js +4 -0
- package/dist/web/assets/NotFoundPage-BCMZbeB8.js +1 -0
- package/dist/web/assets/OverviewPage-0CFWCG9-.css +1 -0
- package/dist/web/assets/OverviewPage-0mYifpjp.js +1 -0
- package/dist/web/assets/PersonalProfilePage-Chv2a2pi.css +1 -0
- package/dist/web/assets/PersonalProfilePage-JW6Muix6.js +1 -0
- package/dist/web/assets/PersonalProjectsPage-CRbi64JP.js +1 -0
- package/dist/web/assets/ProjectAgentFirstTask-DJ0wTbh8.js +1 -0
- package/dist/web/assets/ProjectAgentsPage-CPgKVLgg.js +12 -0
- package/dist/web/assets/ProjectAgentsPage-DeuRkMp1.css +1 -0
- package/dist/web/assets/ProjectScopePicker-BwB7gK-7.js +1 -0
- package/dist/web/assets/ProjectScopePicker-DYA7L1Ex.css +1 -0
- package/dist/web/assets/PublicProjectsPage-CB272Sq-.js +1 -0
- package/dist/web/assets/ReviewPage-CelTZU4C.js +1 -0
- package/dist/web/assets/RoundtablesPage-C9dXfWCl.js +2 -0
- package/dist/web/assets/{SearchableSelect-Do6X3fXi.js → SearchableSelect-f7EHbyDL.js} +1 -1
- package/dist/web/assets/SettingsPage-5g7mRN2F.css +1 -0
- package/dist/web/assets/SettingsPage-nAijmm5f.js +1 -0
- package/dist/web/assets/SharedJefaReportPage-CjpJ9bkk.js +1 -0
- package/dist/web/assets/SharedJefaReportPage-D92FSPru.css +1 -0
- package/dist/web/assets/UiDisclosure-CWlC91Zt.js +1 -0
- package/dist/web/assets/WritingTastePage-DaHWY61P.js +1 -0
- package/dist/web/assets/WritingTastePage-SMGF39mf.css +1 -0
- package/dist/web/assets/agent-profile-CBvJHGjt.css +1 -0
- package/dist/web/assets/{task-evidence-DTiPCs7r.js → agent-work-summary-LeW-Al_S.js} +1 -1
- package/dist/web/assets/attention-store-Dk-_P4Oi.js +1 -0
- package/dist/web/assets/{catalog-JmKyJnKL.js → catalog-DN72452f.js} +1 -1
- package/dist/web/assets/{client-DTQ_tUr2.js → client-B2cRl-7c.js} +1 -1
- package/dist/web/assets/console-BtPqo79j.js +1 -0
- package/dist/web/assets/{conversation-launcher-settings-fUWCt3XG.js → conversation-launcher-settings-jkHRmWai.js} +1 -1
- package/dist/web/assets/{copy-CX6pyzAG.js → copy-D2LqwtPZ.js} +1 -1
- package/dist/web/assets/{i18n-CIIxzhPZ.js → i18n-D2CJkEYK.js} +2 -2
- package/dist/web/assets/{identity-CdFITIOu.js → identity-B3zEw-1u.js} +1 -1
- package/dist/web/assets/index-CNp0tRNp.css +1 -0
- package/dist/web/assets/index-ulISYH02.js +5 -0
- package/dist/web/assets/{model-CJsrDUNb.js → model-S7ehAjky.js} +1 -1
- package/dist/web/assets/{personal-profile-graph-CQLOait6.js → personal-profile-graph--GG3Mizo.js} +1 -1
- package/dist/web/assets/shared-report-CGlCpmNd.css +1 -0
- package/dist/web/assets/shared-report-DEK9wvhl.js +1 -0
- package/dist/web/assets/useAgentRoster-BAPKWZ9y.js +1 -0
- package/dist/web/assets/{useApi-CROJJdhE-fcdVGInY.js → useApi-CROJJdhE-BtFMXFjw.js} +1 -1
- package/dist/web/assets/{useModalDialog-B-45idX9.js → useModalDialog-D8tiJfbc.js} +1 -1
- package/dist/web/assets/{vue-router-BUX6njIv.js → vue-router-DkaTtPn0.js} +1 -1
- package/dist/web/index.html +16 -15
- package/docs/project-agent-memory.md +6 -0
- package/docs/roundtable.md +46 -0
- package/graph-provider/fuli_graph/app.py +3 -1
- package/graph-provider/pyproject.toml +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -4
- package/skills/capturing-session-knowledge/SKILL.md +5 -1
- package/skills/operating-fuli/SKILL.md +4 -0
- package/src/agent-tools/graph-definitions.js +2 -2
- package/src/agents/claude-code/lifecycle-hook.js +6 -19
- package/src/agents/codex/lifecycle-hook.js +58 -27
- package/src/agents/codex/roundtable-config.js +3 -0
- package/src/agents/codex/token-usage.js +235 -0
- package/src/agents/cursor/lifecycle-hook.js +4 -9
- package/src/agents/identity-receipt.js +11 -4
- package/src/agents/token-usage-resolver.js +147 -0
- package/src/conversations/current-task-lookup.js +40 -0
- package/src/conversations/sync-transcript.js +4 -2
- package/src/graphiti/federated-application.js +6 -1
- package/src/graphiti/project-agent-mapping.js +1 -1
- package/src/graphiti/project-agent-workflows.js +6 -1
- package/src/graphiti/provider-client.js +7 -4
- package/src/graphiti/task-entry-preferences.js +6 -7
- package/src/http/static-handler.js +3 -1
- package/src/mcp/instructions.js +8 -7
- package/src/mcp/session-id.js +1 -1
- package/src/mcp/tool-annotations.js +3 -1
- package/src/mcp/tool-result.js +20 -8
- package/src/native-runtime/runtime.js +41 -6
- package/src/roundtables/domain.js +3 -3
- package/src/roundtables/http-router.js +5 -3
- package/src/roundtables/mcp-server.js +15 -8
- package/src/roundtables/profiles.js +84 -0
- package/src/roundtables/service.js +86 -2
- package/src/roundtables/store.js +4 -0
- package/src/roundtables/tool-contract.js +22 -5
- package/src/roundtables/turn-prompt.js +3 -0
- package/dist/web/assets/AboutPage-u2Bv6cO-.js +0 -1
- package/dist/web/assets/AboutPage-vYIBifKw.css +0 -1
- package/dist/web/assets/AgentHand-CsMcNXUm.js +0 -1
- package/dist/web/assets/AgentHand-D9sQu2vc.css +0 -1
- package/dist/web/assets/AgentName-BboCA-Fd.js +0 -1
- package/dist/web/assets/AgentName-Cydd0UI4.css +0 -1
- package/dist/web/assets/AgentProfilePage-Dyux6qE3.js +0 -1
- package/dist/web/assets/AgentsDirectoryPage-BEJdvmX7.js +0 -1
- package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +0 -1
- package/dist/web/assets/ConnectionsPage-Dw_hK_EM.js +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-B59X3rOi.css +0 -1
- package/dist/web/assets/EmployeeRecruitDialog-DP_ktv3w.js +0 -1
- package/dist/web/assets/EmployeeWorkbenchPage-CAt8lVnr.js +0 -3
- package/dist/web/assets/EmployeeWorkbenchPage-CQwZnTA0.css +0 -1
- package/dist/web/assets/GrowthLoading-B_Hh0CRS.css +0 -1
- package/dist/web/assets/GrowthLoading-DIwqGFSc.js +0 -1
- package/dist/web/assets/KnowledgeEditDialog-D9NuRHvp.css +0 -1
- package/dist/web/assets/KnowledgeEditDialog-Degd9AGD.js +0 -1
- package/dist/web/assets/KnowledgeInspector--1NujtXW.css +0 -1
- package/dist/web/assets/KnowledgeInspector-yms1ysZ4.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-C64wNn8a.js +0 -1
- package/dist/web/assets/KnowledgeOrganizerPage-h2iKvQpO.css +0 -1
- package/dist/web/assets/KnowledgePage-BTFEXzgi.js +0 -1
- package/dist/web/assets/KnowledgeWorkspace-BsF1X4Aq.js +0 -4
- package/dist/web/assets/NotFoundPage-DfaNTiD1.js +0 -1
- package/dist/web/assets/OverviewPage-VXMfcWFI.js +0 -1
- package/dist/web/assets/PersonalProfilePage-C1LBhcYu.js +0 -1
- package/dist/web/assets/PersonalProfilePage-L8T5xMEC.css +0 -1
- package/dist/web/assets/PersonalProjectsPage-B5lV4ZJW.js +0 -1
- package/dist/web/assets/ProjectAgentFirstTask-DF-IvEqp.js +0 -1
- package/dist/web/assets/ProjectAgentsPage-CgL92gng.js +0 -13
- package/dist/web/assets/ProjectAgentsPage-CoJ4qQlA.css +0 -1
- package/dist/web/assets/ProjectScopePicker-BvaGTMFx.css +0 -1
- package/dist/web/assets/ProjectScopePicker-CZUMdyYz.js +0 -1
- package/dist/web/assets/PublicProjectsPage-e4CpxHk9.js +0 -1
- package/dist/web/assets/ReviewPage-DugEaFvI.js +0 -1
- package/dist/web/assets/RoundtablesPage-642YtZBC.js +0 -2
- package/dist/web/assets/SettingsPage-Brl-SL33.css +0 -1
- package/dist/web/assets/SettingsPage-wqYxDPyu.js +0 -1
- package/dist/web/assets/SharedJefaReportPage-CNQDezJr.js +0 -1
- package/dist/web/assets/SharedJefaReportPage-DXbj_JhO.css +0 -1
- package/dist/web/assets/WritingTastePage-Bzg6AvnB.css +0 -1
- package/dist/web/assets/WritingTastePage-CAUvbDlL.js +0 -1
- package/dist/web/assets/agent-profile-CdKOh5xw.css +0 -1
- package/dist/web/assets/agent-profile.css_vue_type_style_index_0_src_true_lang-DHOcWSe_.js +0 -1
- package/dist/web/assets/console-CrhIK67Y.js +0 -1
- package/dist/web/assets/employee-dialog-Bh5ccIbK.css +0 -1
- package/dist/web/assets/index-CYZi7E1e.css +0 -1
- package/dist/web/assets/index-Dsgn8o4-.js +0 -2
- package/dist/web/assets/shared-report-Cll2Yj8W.js +0 -1
- package/dist/web/assets/shared-report-ezeb8dW4.css +0 -1
- package/docs/roundtable-beta-testing.md +0 -102
- package/docs/roundtable-beta-validation.md +0 -133
- package/docs/roundtable-research.md +0 -142
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, opendir, open } from 'node:fs/promises';
|
|
3
|
+
import { basename, extname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const MAX_HEADER_BYTES = 64 * 1024;
|
|
6
|
+
const MAX_USAGE_TAIL_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
const MAX_CANDIDATE_FILES = 128;
|
|
8
|
+
const MAX_DIRECTORY_ENTRIES = 2048;
|
|
9
|
+
const MAX_DIRECTORY_DEPTH = 8;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Codex's native JSONL session adapter. It returns only the latest cumulative
|
|
13
|
+
* token_count snapshot and never returns transcript records or text.
|
|
14
|
+
*/
|
|
15
|
+
export function createCodexTokenUsageResolver({
|
|
16
|
+
sessionsDirectory = undefined,
|
|
17
|
+
archivedSessionsDirectory = undefined,
|
|
18
|
+
codexSessionsDirectory = undefined,
|
|
19
|
+
codexArchivedSessionsDirectory = undefined,
|
|
20
|
+
codexHomeDirectory = undefined,
|
|
21
|
+
homeDirectory = undefined
|
|
22
|
+
} = {}) {
|
|
23
|
+
const explicitSessionsDirectory = sessionsDirectory ?? codexSessionsDirectory;
|
|
24
|
+
const explicitArchivedSessionsDirectory =
|
|
25
|
+
archivedSessionsDirectory ?? codexArchivedSessionsDirectory;
|
|
26
|
+
const home = typeof homeDirectory === 'string' && homeDirectory
|
|
27
|
+
? homeDirectory : defaultHomeDirectory();
|
|
28
|
+
const codexHome = typeof codexHomeDirectory === 'string' && codexHomeDirectory
|
|
29
|
+
? codexHomeDirectory
|
|
30
|
+
: homeDirectory === undefined ? (defaultCodexHomeDirectory() ?? (home ? join(home, '.codex') : null))
|
|
31
|
+
: (home ? join(home, '.codex') : null);
|
|
32
|
+
const useDefaultRoots = explicitSessionsDirectory === undefined &&
|
|
33
|
+
explicitArchivedSessionsDirectory === undefined;
|
|
34
|
+
const roots = [
|
|
35
|
+
explicitSessionsDirectory ?? (useDefaultRoots && codexHome ? join(codexHome, 'sessions') : null),
|
|
36
|
+
explicitArchivedSessionsDirectory ??
|
|
37
|
+
(useDefaultRoots && codexHome ? join(codexHome, 'archived_sessions') : null)
|
|
38
|
+
]
|
|
39
|
+
.filter((root, index, values) => typeof root === 'string' && root && values.indexOf(root) === index);
|
|
40
|
+
return {
|
|
41
|
+
resolve: (request) => resolveCodexTokenUsage(request, { roots })
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function resolveCodexTokenUsage(request = {}, { roots } = {}) {
|
|
46
|
+
const sessionId = safeSessionId(request.sessionId ?? request.session_id);
|
|
47
|
+
if (!sessionId) return null;
|
|
48
|
+
const configuredRoots = Array.isArray(roots) ? roots : roots ? [roots] : null;
|
|
49
|
+
const directories = (configuredRoots ?? [
|
|
50
|
+
defaultCodexSessionsDirectory(),
|
|
51
|
+
defaultCodexArchivedSessionsDirectory()
|
|
52
|
+
]).filter((root, index, values) => typeof root === 'string' && root && values.indexOf(root) === index);
|
|
53
|
+
|
|
54
|
+
let inspectedEntries = 0;
|
|
55
|
+
let candidates = 0;
|
|
56
|
+
for (const root of directories) {
|
|
57
|
+
const files = await matchingSessionFiles(root, sessionId, {
|
|
58
|
+
onEntry: () => { inspectedEntries += 1; },
|
|
59
|
+
get entriesInspected() { return inspectedEntries; },
|
|
60
|
+
get candidatesFound() { return candidates; },
|
|
61
|
+
addCandidate: () => { candidates += 1; }
|
|
62
|
+
});
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
const usage = await readSessionUsage(file, sessionId);
|
|
65
|
+
if (usage) return usage;
|
|
66
|
+
}
|
|
67
|
+
if (inspectedEntries >= MAX_DIRECTORY_ENTRIES || candidates >= MAX_CANDIDATE_FILES) break;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function matchingSessionFiles(root, sessionId, counters, depth = 0) {
|
|
73
|
+
if (depth > MAX_DIRECTORY_DEPTH || counters.entriesInspected >= MAX_DIRECTORY_ENTRIES ||
|
|
74
|
+
counters.candidatesFound >= MAX_CANDIDATE_FILES) return [];
|
|
75
|
+
let directory;
|
|
76
|
+
try {
|
|
77
|
+
const rootStat = await lstat(root);
|
|
78
|
+
if (!rootStat.isDirectory()) return [];
|
|
79
|
+
directory = await opendir(root);
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const files = [];
|
|
85
|
+
try {
|
|
86
|
+
for await (const entry of directory) {
|
|
87
|
+
counters.onEntry();
|
|
88
|
+
if (counters.entriesInspected > MAX_DIRECTORY_ENTRIES ||
|
|
89
|
+
counters.candidatesFound >= MAX_CANDIDATE_FILES) break;
|
|
90
|
+
const path = join(root, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
files.push(...await matchingSessionFiles(path, sessionId, counters, depth + 1));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!entry.isFile() || extname(entry.name) !== '.jsonl' ||
|
|
96
|
+
!basename(entry.name).includes(sessionId)) continue;
|
|
97
|
+
counters.addCandidate();
|
|
98
|
+
files.push(path);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
// A session directory can rotate while the host is running. An incomplete
|
|
102
|
+
// scan is an unavailable snapshot, never a reason to guess another file.
|
|
103
|
+
} finally {
|
|
104
|
+
await directory.close().catch(() => {});
|
|
105
|
+
}
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function readSessionUsage(filePath, sessionId) {
|
|
110
|
+
let file;
|
|
111
|
+
try {
|
|
112
|
+
file = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
113
|
+
const stat = await file.stat();
|
|
114
|
+
if (!stat.isFile() || stat.size <= 0) return null;
|
|
115
|
+
|
|
116
|
+
const header = await readBytes(file, Math.min(MAX_HEADER_BYTES, stat.size), 0);
|
|
117
|
+
const firstLineEnd = header.indexOf(10);
|
|
118
|
+
if (firstLineEnd < 0 || !isSessionHeader(header.subarray(0, firstLineEnd), sessionId)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const start = Math.max(0, stat.size - MAX_USAGE_TAIL_BYTES);
|
|
123
|
+
const tail = await readBytes(file, stat.size - start, start);
|
|
124
|
+
let firstLine = 0;
|
|
125
|
+
if (start > 0) {
|
|
126
|
+
const previous = await readBytes(file, 1, start - 1);
|
|
127
|
+
if (previous[0] !== 10) {
|
|
128
|
+
firstLine = tail.indexOf(10) + 1;
|
|
129
|
+
if (firstLine <= 0) return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return latestTokenUsage(tail.subarray(firstLine));
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
} finally {
|
|
136
|
+
await file?.close().catch(() => {});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function readBytes(file, length, position) {
|
|
141
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
142
|
+
let offset = 0;
|
|
143
|
+
while (offset < length) {
|
|
144
|
+
const result = await file.read(buffer, offset, length - offset, position + offset);
|
|
145
|
+
if (!result.bytesRead) break;
|
|
146
|
+
offset += result.bytesRead;
|
|
147
|
+
}
|
|
148
|
+
return offset === length ? buffer : buffer.subarray(0, offset);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isSessionHeader(bytes, sessionId) {
|
|
152
|
+
try {
|
|
153
|
+
const record = JSON.parse(bytes.toString('utf8'));
|
|
154
|
+
return record?.type === 'session_meta' && record.payload?.id === sessionId;
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function latestTokenUsage(bytes) {
|
|
161
|
+
let latest = null;
|
|
162
|
+
const text = bytes.toString('utf8');
|
|
163
|
+
let offset = 0;
|
|
164
|
+
while (offset < text.length) {
|
|
165
|
+
const end = text.indexOf('\n', offset);
|
|
166
|
+
const line = text.slice(offset, end < 0 ? text.length : end).trim();
|
|
167
|
+
if (line.includes('"token_count"') && line.includes('"total_token_usage"')) {
|
|
168
|
+
try {
|
|
169
|
+
const record = JSON.parse(line);
|
|
170
|
+
const total = record?.payload?.info?.total_token_usage;
|
|
171
|
+
if (record?.type === 'event_msg' && record.payload?.type === 'token_count') {
|
|
172
|
+
const totalTokens = nonNegativeInteger(total?.total_tokens);
|
|
173
|
+
if (totalTokens !== null) {
|
|
174
|
+
latest = {
|
|
175
|
+
source: 'host',
|
|
176
|
+
totalTokens,
|
|
177
|
+
inputTokens: optionalNonNegativeInteger(total?.input_tokens),
|
|
178
|
+
outputTokens: optionalNonNegativeInteger(total?.output_tokens),
|
|
179
|
+
cachedInputTokens: optionalNonNegativeInteger(total?.cached_input_tokens),
|
|
180
|
+
cacheWriteInputTokens: optionalNonNegativeInteger(total?.cache_write_input_tokens),
|
|
181
|
+
reasoningOutputTokens: optionalNonNegativeInteger(total?.reasoning_output_tokens)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// Ignore malformed native records and continue to later snapshots.
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (end < 0) break;
|
|
190
|
+
offset = end + 1;
|
|
191
|
+
}
|
|
192
|
+
return latest;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function optionalNonNegativeInteger(value) {
|
|
196
|
+
return value === null || value === undefined ? null : nonNegativeInteger(value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function nonNegativeInteger(value) {
|
|
200
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function safeSessionId(value) {
|
|
204
|
+
if (typeof value !== 'string') return null;
|
|
205
|
+
const normalized = value.trim();
|
|
206
|
+
if (!normalized || normalized.length > 256 || normalized.includes('..') ||
|
|
207
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) return null;
|
|
208
|
+
return normalized;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function defaultCodexSessionsDirectory() {
|
|
212
|
+
const codexHome = defaultCodexRootDirectory();
|
|
213
|
+
return codexHome ? join(codexHome, 'sessions') : null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function defaultCodexArchivedSessionsDirectory() {
|
|
217
|
+
const codexHome = defaultCodexRootDirectory();
|
|
218
|
+
return codexHome ? join(codexHome, 'archived_sessions') : null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function defaultCodexRootDirectory() {
|
|
222
|
+
const configured = defaultCodexHomeDirectory();
|
|
223
|
+
if (configured) return configured;
|
|
224
|
+
const home = defaultHomeDirectory();
|
|
225
|
+
return home ? join(home, '.codex') : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function defaultCodexHomeDirectory() {
|
|
229
|
+
const value = process.env.CODEX_HOME;
|
|
230
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function defaultHomeDirectory() {
|
|
234
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? null;
|
|
235
|
+
}
|
|
@@ -6,7 +6,6 @@ import { callAgentTool } from '../../agent-tools.js';
|
|
|
6
6
|
import { createRuntimeLeaseClient } from '../../adaptive-runtime/lease-client.js';
|
|
7
7
|
import { openFederatedGraphApplication } from '../../graphiti/federated-application.js';
|
|
8
8
|
import { resolveGraphRuntimeOptions } from '../../graphiti/runtime-config.js';
|
|
9
|
-
import { boundedHookMessage } from '../lifecycle-message.js';
|
|
10
9
|
|
|
11
10
|
const EVENTS = new Set(['sessionStart', 'beforeSubmitPrompt', 'stop']);
|
|
12
11
|
|
|
@@ -18,14 +17,9 @@ export async function cursorLifecycleOutput(event, input, invoke) {
|
|
|
18
17
|
}
|
|
19
18
|
const source = { sourceApplication: 'cursor', sourceSessionId: sessionId };
|
|
20
19
|
if (event === 'stop') {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const notice = '\nIf this mode is read-only or checkpoint writes are denied, state that the checkpoint was not saved and do not retry. This is the only checkpoint continuation.';
|
|
25
|
-
const reason = boundedHookMessage(check.reason, { maxBytes: 8_000 - Buffer.byteLength(notice, 'utf8') });
|
|
26
|
-
return check.status === 'checkpoint_required' && reason.trim()
|
|
27
|
-
? { followup_message: reason + notice }
|
|
28
|
-
: {};
|
|
20
|
+
// Cursor has no advisory Stop output. A followup_message would start
|
|
21
|
+
// another model turn, so pending checkpoints remain available through MCP.
|
|
22
|
+
return {};
|
|
29
23
|
}
|
|
30
24
|
const roots = input.workspace_roots;
|
|
31
25
|
if (!Array.isArray(roots) || roots.length !== 1 || typeof roots[0] !== 'string') {
|
|
@@ -50,6 +44,7 @@ export async function cursorLifecycleOutput(event, input, invoke) {
|
|
|
50
44
|
+ `get_collaboration_preferences with sessionId=${JSON.stringify(sessionId)}, `
|
|
51
45
|
+ 'projectPath=current working directory and taskPrompt=current request '
|
|
52
46
|
+ 'to restore the role selected for that exact prompt and its latest project memory. '
|
|
47
|
+
+ 'Every final reply MUST include this task’s agent_receipt.markdown unchanged exactly once, including greetings and capture-disabled tasks. '
|
|
53
48
|
+ 'Before finishing, call '
|
|
54
49
|
+ `verify_task_checkpoint with sessionId=${JSON.stringify(sessionId)}; use its task_context_token `
|
|
55
50
|
+ 'to checkpoint_task_knowledge, including agentMemory if the role context changed. '
|
|
@@ -14,11 +14,18 @@ export function agentContinuation(projectId, agentId, conversationId = null) {
|
|
|
14
14
|
|
|
15
15
|
export function taskAgentReceipt(application, { projectId, agentId, name = null,
|
|
16
16
|
sourceApplication = null, workStatus = null, conversationId = null, persistence = null }) {
|
|
17
|
-
|
|
18
|
-
profile_url: agentProfileUrl(application, agentId), source_application: sourceApplication }
|
|
17
|
+
const owner = agentId ? { agent_id: agentId, name,
|
|
18
|
+
profile_url: agentProfileUrl(application, agentId), source_application: sourceApplication } : null;
|
|
19
|
+
const label = String(name?.trim() || agentId).replace(/[\r\n\t]+/g, ' ')
|
|
20
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
21
|
+
.replace(/[\\`*_{}\[\]()!|]/g, '\\$&');
|
|
22
|
+
const markdown = owner
|
|
23
|
+
? `FULI Agent:[${label}](<${owner.profile_url.replace(/[<>\s]/g, encodeURIComponent)}>)`
|
|
24
|
+
: 'FULI Agent:未选定';
|
|
25
|
+
return { required: true, markdown, owner,
|
|
19
26
|
personal_project_id: projectId, work_status: workStatus, persistence,
|
|
20
|
-
continuation: agentContinuation(projectId, agentId, conversationId),
|
|
21
|
-
guidance: '
|
|
27
|
+
continuation: owner ? agentContinuation(projectId, agentId, conversationId) : null,
|
|
28
|
+
guidance: 'MUST include agent_receipt.markdown unchanged exactly once in every final user-visible reply, including greetings, status and errors, even when capture is disabled or nothing is retained. Use this task’s receipt; never invent an owner. Report only truthful work/memory status and actually used collaboration_receipt participants; tools and configured peers are not independent model workers.' };
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
export function employeeCollaborationReceipt(application, context, { tool, permission, sourceApplication }) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createCodexTokenUsageResolver } from './codex/token-usage.js';
|
|
2
|
+
|
|
3
|
+
const TOKEN_USAGE_SOURCES = new Set(['executor', 'host', 'dingdong']);
|
|
4
|
+
const DEFAULT_RESOLVER = createAgentTokenUsageResolver();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve one exact worker/session identity through the runtime adapter port.
|
|
8
|
+
*
|
|
9
|
+
* A worker runtime is authoritative when present. Its session is never
|
|
10
|
+
* replaced with the reporting host session. A reporting host session is used
|
|
11
|
+
* only when no worker runtime was supplied, which covers direct host activity.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveAgentSession(input = {}) {
|
|
14
|
+
const workerRuntime = input.workerRuntime ?? input.worker_runtime;
|
|
15
|
+
if (workerRuntime !== undefined && workerRuntime !== null) {
|
|
16
|
+
if (!workerRuntime || typeof workerRuntime !== 'object' || Array.isArray(workerRuntime)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const application = workerRuntime.application;
|
|
20
|
+
const sessionId = safeSessionId(workerRuntime.sessionId ?? workerRuntime.session_id);
|
|
21
|
+
if (!validApplication(application) || !sessionId) return null;
|
|
22
|
+
return {
|
|
23
|
+
application,
|
|
24
|
+
sessionId,
|
|
25
|
+
sourceApplication: input.sourceApplication ?? input.source_application ?? null,
|
|
26
|
+
sourceSessionId: input.sourceSessionId ?? input.source_session_id ?? null,
|
|
27
|
+
scope: 'worker'
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (hasWorkerMetadata(input)) return null;
|
|
32
|
+
const application = input.sourceApplication ?? input.source_application;
|
|
33
|
+
const sessionId = safeSessionId(input.sourceSessionId ?? input.source_session_id);
|
|
34
|
+
if (!validApplication(application) || !sessionId) return null;
|
|
35
|
+
return {
|
|
36
|
+
application,
|
|
37
|
+
sessionId,
|
|
38
|
+
sourceApplication: application,
|
|
39
|
+
sourceSessionId: sessionId,
|
|
40
|
+
scope: 'host'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Shared adapter port for exact session token usage.
|
|
46
|
+
*
|
|
47
|
+
* Adapters receive only the selected session identity and return a cumulative
|
|
48
|
+
* snapshot or null. They must not return transcript content.
|
|
49
|
+
*/
|
|
50
|
+
export function createAgentTokenUsageResolver({ adapters } = {}) {
|
|
51
|
+
const registry = adapters instanceof Map
|
|
52
|
+
? adapters
|
|
53
|
+
: new Map(Object.entries(adapters ?? {
|
|
54
|
+
codex: createCodexTokenUsageResolver()
|
|
55
|
+
}));
|
|
56
|
+
return {
|
|
57
|
+
async resolve(input = {}) {
|
|
58
|
+
const session = resolveAgentSession(input);
|
|
59
|
+
if (!session) return null;
|
|
60
|
+
const adapter = registry.get(session.application);
|
|
61
|
+
const resolve = typeof adapter === 'function' ? adapter : adapter?.resolve;
|
|
62
|
+
if (typeof resolve !== 'function') return null;
|
|
63
|
+
try {
|
|
64
|
+
return normalizeTokenUsage(await resolve.call(adapter, session));
|
|
65
|
+
} catch {
|
|
66
|
+
// Usage collection is best-effort. It must not prevent the host from
|
|
67
|
+
// recording otherwise valid worker activity.
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function enrichProjectAgentTaskActivity(input = {}, resolver = DEFAULT_RESOLVER) {
|
|
75
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return input;
|
|
76
|
+
const actorKind = input.actorKind ?? input.actor_kind;
|
|
77
|
+
if (actorKind === 'human') return input;
|
|
78
|
+
// Null is an explicit report (including an explicit zero inside the object),
|
|
79
|
+
// so only an omitted value is eligible for automatic collection.
|
|
80
|
+
const explicitTokenUsage = (Object.hasOwn(input, 'tokenUsage') && input.tokenUsage !== undefined) ||
|
|
81
|
+
(Object.hasOwn(input, 'token_usage') && input.token_usage !== undefined);
|
|
82
|
+
if (explicitTokenUsage) return input;
|
|
83
|
+
|
|
84
|
+
const resolve = typeof resolver === 'function' ? resolver : resolver?.resolve;
|
|
85
|
+
if (typeof resolve !== 'function') return input;
|
|
86
|
+
try {
|
|
87
|
+
const tokenUsage = normalizeTokenUsage(await resolve.call(resolver, input));
|
|
88
|
+
return tokenUsage ? { ...input, tokenUsage } : input;
|
|
89
|
+
} catch {
|
|
90
|
+
return input;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function resolveProjectAgentTaskActivityTokenUsage(input, resolver = DEFAULT_RESOLVER) {
|
|
95
|
+
const enriched = await enrichProjectAgentTaskActivity(input, resolver);
|
|
96
|
+
if (!enriched || typeof enriched !== 'object') return null;
|
|
97
|
+
return enriched.tokenUsage !== undefined ? enriched.tokenUsage : enriched.token_usage ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeTokenUsage(value) {
|
|
101
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
102
|
+
const source = value.source;
|
|
103
|
+
const totalTokens = nonNegativeInteger(value.totalTokens ?? value.total_tokens);
|
|
104
|
+
if (!TOKEN_USAGE_SOURCES.has(source) || totalTokens === null) return null;
|
|
105
|
+
return {
|
|
106
|
+
source,
|
|
107
|
+
totalTokens,
|
|
108
|
+
inputTokens: optionalNonNegativeInteger(value.inputTokens ?? value.input_tokens),
|
|
109
|
+
outputTokens: optionalNonNegativeInteger(value.outputTokens ?? value.output_tokens),
|
|
110
|
+
cachedInputTokens: optionalNonNegativeInteger(value.cachedInputTokens ?? value.cached_input_tokens),
|
|
111
|
+
cacheWriteInputTokens: optionalNonNegativeInteger(
|
|
112
|
+
value.cacheWriteInputTokens ?? value.cache_write_input_tokens
|
|
113
|
+
),
|
|
114
|
+
reasoningOutputTokens: optionalNonNegativeInteger(
|
|
115
|
+
value.reasoningOutputTokens ?? value.reasoning_output_tokens
|
|
116
|
+
)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function optionalNonNegativeInteger(value) {
|
|
121
|
+
return value === null || value === undefined ? null : nonNegativeInteger(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function nonNegativeInteger(value) {
|
|
125
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function validApplication(value) {
|
|
129
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 64;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasWorkerMetadata(input) {
|
|
133
|
+
return [
|
|
134
|
+
input.workerId ?? input.worker_id,
|
|
135
|
+
input.workerLabel ?? input.worker_label,
|
|
136
|
+
input.workerStatus ?? input.worker_status,
|
|
137
|
+
input.workerOccupationEmoji ?? input.worker_occupation_emoji
|
|
138
|
+
].some((value) => value !== undefined && value !== null && value !== '');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function safeSessionId(value) {
|
|
142
|
+
if (typeof value !== 'string') return null;
|
|
143
|
+
const normalized = value.trim();
|
|
144
|
+
if (!normalized || normalized.length > 256 || normalized.includes('..') ||
|
|
145
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) return null;
|
|
146
|
+
return normalized;
|
|
147
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ProviderRequestError } from '../graphiti/provider-client.js';
|
|
2
|
+
|
|
3
|
+
// Only this first, read-only lookup can be deferred before a scope is known.
|
|
4
|
+
// Each real HTTP attempt is bounded; writes and later handoff operations are
|
|
5
|
+
// never retried here or mistaken for a compatibility failure.
|
|
6
|
+
export async function lookupTranscriptTask(application, input, sourceApplication) {
|
|
7
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8
|
+
try {
|
|
9
|
+
const task = await application.personal.currentTaskContext({
|
|
10
|
+
personal_space_id: application.config.personal.spaceId,
|
|
11
|
+
session_id: input.session_id, source_application: sourceApplication
|
|
12
|
+
}, { timeoutMs: 1500 });
|
|
13
|
+
if (task !== null && (!task || typeof task !== 'object' || Array.isArray(task)
|
|
14
|
+
|| typeof task.token !== 'string' || !task.token
|
|
15
|
+
|| !Object.hasOwn(task, 'project_agent_id')
|
|
16
|
+
|| (task.project_agent_id !== null && (typeof task.project_agent_id !== 'string'
|
|
17
|
+
|| !task.project_agent_id || typeof task.personal_project_id !== 'string' || !task.personal_project_id))
|
|
18
|
+
|| (task.session_id !== undefined && task.session_id !== input.session_id)
|
|
19
|
+
|| (task.source_application !== undefined && task.source_application !== sourceApplication)
|
|
20
|
+
|| (task.personal_space_id !== undefined && task.personal_space_id !== application.config.personal.spaceId))) {
|
|
21
|
+
throw new ProviderRequestError('Current task response does not match its requested scope', { code: 'provider_invalid_response' });
|
|
22
|
+
}
|
|
23
|
+
return { task };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (!(error instanceof ProviderRequestError)) throw error;
|
|
26
|
+
if (error.status === 404 && error.code === 'provider_error'
|
|
27
|
+
&& error.diagnostic?.status === 404 && error.diagnostic.detail === 'Not Found') {
|
|
28
|
+
return { failure: { status: 'unsupported', entryBlocked: true,
|
|
29
|
+
code: 'current_task_endpoint_unavailable',
|
|
30
|
+
reason: 'Visible transcript synchronization is not available on this Fuli provider version' } };
|
|
31
|
+
}
|
|
32
|
+
const transient = ['provider_unavailable', 'provider_timeout'].includes(error.code)
|
|
33
|
+
|| (error.code === 'provider_http_5xx' && [502, 503, 504].includes(error.status));
|
|
34
|
+
if (!transient) throw error;
|
|
35
|
+
if (attempt === 1) return { failure: { status: 'unsaved', entryBlocked: true,
|
|
36
|
+
code: 'current_task_lookup_unavailable',
|
|
37
|
+
reason: 'Visible transcript synchronization is temporarily unavailable after bounded retries; native history remains available for a later verified task entry' } };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { initialTranscriptBoundary, readTranscriptBatch } from './transcript-reader.js';
|
|
2
2
|
import { transcriptDigest } from './transcript-guard.js';
|
|
3
|
+
import { lookupTranscriptTask } from './current-task-lookup.js';
|
|
3
4
|
|
|
4
5
|
const pending = reason => ({ status: 'partial', entryBlocked: true, reason });
|
|
5
6
|
const retryReason = 'Transcript handoff is pending. Retry task entry to continue its saved progress; no new Agent context was supplied';
|
|
@@ -9,8 +10,9 @@ export async function syncConversationTranscript(application, input, sourceAppli
|
|
|
9
10
|
if (!input?.transcript_path) return { status: 'partial', reason: 'Host did not supply a visible transcript' };
|
|
10
11
|
let committedPages = 0;
|
|
11
12
|
try {
|
|
12
|
-
const
|
|
13
|
-
|
|
13
|
+
const lookup = await lookupTranscriptTask(application, input, sourceApplication);
|
|
14
|
+
if (lookup.failure) return lookup.failure;
|
|
15
|
+
const task = lookup.task;
|
|
14
16
|
if (!task?.project_agent_id) return guard?.value ? pending(retryReason) : { status: 'unassigned' };
|
|
15
17
|
const scope = { personal_space_id: application.config.personal.spaceId, personal_project_id: task.personal_project_id,
|
|
16
18
|
agent_id: task.project_agent_id, source_application: sourceApplication };
|
|
@@ -92,6 +92,7 @@ export function openFederatedGraphApplication({
|
|
|
92
92
|
config,
|
|
93
93
|
capturePolicyStore,
|
|
94
94
|
agentAccessPolicyStore,
|
|
95
|
+
tokenUsageResolver,
|
|
95
96
|
fetchImpl = globalThis.fetch,
|
|
96
97
|
env = process.env
|
|
97
98
|
}) {
|
|
@@ -105,6 +106,7 @@ export function openFederatedGraphApplication({
|
|
|
105
106
|
agentAccessPolicyStore: agentAccessPolicyStore ?? new AgentAccessPolicyStore(
|
|
106
107
|
agentAccessPolicyPathForRuntime(runtimeConfigPath)
|
|
107
108
|
),
|
|
109
|
+
tokenUsageResolver,
|
|
108
110
|
consoleUrl: sourceConsoleUrl(runtimeConfigPath),
|
|
109
111
|
providerRequestTimeoutMs: providerRequestTimeoutFromEnv(env)
|
|
110
112
|
});
|
|
@@ -140,7 +142,8 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
140
142
|
consoleUrl = sourceConsoleUrl(null),
|
|
141
143
|
projectPathResolver = resolvePersonalProjectPath,
|
|
142
144
|
taskContextRegistry = null,
|
|
143
|
-
providerRequestTimeoutMs = undefined
|
|
145
|
+
providerRequestTimeoutMs = undefined,
|
|
146
|
+
tokenUsageResolver = undefined
|
|
144
147
|
} = {}) {
|
|
145
148
|
super();
|
|
146
149
|
this.graphiti = true;
|
|
@@ -149,6 +152,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
149
152
|
this.agentAccessPolicyStore = agentAccessPolicyStore;
|
|
150
153
|
this.consoleUrl = consoleUrl;
|
|
151
154
|
this.projectPathResolver = projectPathResolver;
|
|
155
|
+
this.tokenUsageResolver = tokenUsageResolver;
|
|
152
156
|
this.personal = new GraphitiProviderClient({
|
|
153
157
|
baseUrl: config.personal.providerUrl,
|
|
154
158
|
accessToken: config.personal.accessToken,
|
|
@@ -285,6 +289,7 @@ export class FederatedGraphApplication extends ProjectAgentControlPlaneApplicati
|
|
|
285
289
|
return {
|
|
286
290
|
status: 'generated',
|
|
287
291
|
...skill,
|
|
292
|
+
agent_receipt: context.agent_receipt,
|
|
288
293
|
...(context.task_knowledge_recall
|
|
289
294
|
? { task_knowledge_recall: context.task_knowledge_recall }
|
|
290
295
|
: {}),
|
|
@@ -380,7 +380,7 @@ export function providerProjectAgentTaskActivity(input) {
|
|
|
380
380
|
}
|
|
381
381
|
if (hasAny(input, ['tokenUsage', 'token_usage'])) {
|
|
382
382
|
result.token_usage = providerProjectAgentTokenUsage(
|
|
383
|
-
input.tokenUsage
|
|
383
|
+
input.tokenUsage !== undefined ? input.tokenUsage : input.token_usage
|
|
384
384
|
);
|
|
385
385
|
}
|
|
386
386
|
if (hasAny(input, ['workerRuntime', 'worker_runtime'])) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadProjectAgentContinuity } from './project-agent-task-entry.js';
|
|
2
2
|
import { resolveCoordinationScope, scopedTaskIdempotencyKey } from './temporary-project-scope.js';
|
|
3
|
+
import { enrichProjectAgentTaskActivity } from '../agents/token-usage-resolver.js';
|
|
3
4
|
import {
|
|
4
5
|
executorActualReportRecord,
|
|
5
6
|
executorRecord,
|
|
@@ -306,8 +307,12 @@ export async function viewProjectAgentTask(application, input) {
|
|
|
306
307
|
}
|
|
307
308
|
|
|
308
309
|
export async function recordProjectAgentTaskActivity(application, input) {
|
|
310
|
+
const activity = await enrichProjectAgentTaskActivity(
|
|
311
|
+
input,
|
|
312
|
+
application.tokenUsageResolver
|
|
313
|
+
);
|
|
309
314
|
const value = await application.personal.recordProjectAgentTaskActivity(
|
|
310
|
-
providerProjectAgentTaskActivity(
|
|
315
|
+
providerProjectAgentTaskActivity(activity)
|
|
311
316
|
);
|
|
312
317
|
return value?.task ? projectAgentTaskRecord(value.task) : projectAgentTaskRecord(value);
|
|
313
318
|
}
|
|
@@ -72,8 +72,10 @@ export class GraphitiProviderClient {
|
|
|
72
72
|
method: operation === 'policy' ? 'PUT' : 'POST', body: input
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
-
currentTaskContext(input) {
|
|
76
|
-
|
|
75
|
+
currentTaskContext(input, { timeoutMs = this.requestTimeoutMs } = {}) {
|
|
76
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) throw new TypeError('Current task timeout must be a positive safe integer');
|
|
77
|
+
return this.#request(`/v1/task-context-sessions/current?${new URLSearchParams(input)}`,
|
|
78
|
+
{ timeoutMs: Math.min(timeoutMs, this.requestTimeoutMs) });
|
|
77
79
|
}
|
|
78
80
|
health() { return this.#request('/health', { authenticated: false }); }
|
|
79
81
|
listSpaces() { return this.#request('/v1/spaces'); }
|
|
@@ -695,7 +697,8 @@ export class GraphitiProviderClient {
|
|
|
695
697
|
method = 'GET',
|
|
696
698
|
body,
|
|
697
699
|
authenticated = true,
|
|
698
|
-
workflowObservation = false
|
|
700
|
+
workflowObservation = false,
|
|
701
|
+
timeoutMs = this.requestTimeoutMs
|
|
699
702
|
} = {}) {
|
|
700
703
|
const headers = { accept: 'application/json' };
|
|
701
704
|
if (authenticated) headers.authorization = `Bearer ${this.accessToken}`;
|
|
@@ -708,7 +711,7 @@ export class GraphitiProviderClient {
|
|
|
708
711
|
const timeout = setTimeout(() => {
|
|
709
712
|
timedOut = true;
|
|
710
713
|
controller.abort(new Error('Graphiti provider request timed out'));
|
|
711
|
-
},
|
|
714
|
+
}, timeoutMs);
|
|
712
715
|
const callerSignal = activeAgentRequestSignal();
|
|
713
716
|
const signal = callerSignal
|
|
714
717
|
? AbortSignal.any([controller.signal, callerSignal])
|
|
@@ -36,15 +36,14 @@ export async function taskEntryPreferences(application, projectResolution, {
|
|
|
36
36
|
sourceApplication, sourceSessionId, sessionId })
|
|
37
37
|
: null;
|
|
38
38
|
const managedPreferences = management ? { ...preferences, project_management_context: management } : preferences;
|
|
39
|
-
|
|
40
|
-
const context = selection.agent ? await loadProjectAgentContinuity(application, {
|
|
39
|
+
const context = selection?.agent ? await loadProjectAgentContinuity(application, {
|
|
41
40
|
projectId: projectResolution.personalProjectId, agent: selection.agent,
|
|
42
41
|
sourceApplication, taskPrompt, selectionReason: selection.reason,
|
|
43
42
|
matchBasis: selection.match_basis
|
|
44
43
|
}) : selection;
|
|
45
|
-
return { ...managedPreferences, project_agent_context: context,
|
|
46
|
-
|
|
47
|
-
projectId: projectResolution.personalProjectId, agentId: selection
|
|
48
|
-
name: context
|
|
49
|
-
}) }
|
|
44
|
+
return { ...managedPreferences, ...(context ? { project_agent_context: context } : {}),
|
|
45
|
+
agent_receipt: taskAgentReceipt(application, {
|
|
46
|
+
projectId: projectResolution.personalProjectId, agentId: selection?.agent?.agentId ?? null,
|
|
47
|
+
name: context?.role?.name, sourceApplication
|
|
48
|
+
}) };
|
|
50
49
|
}
|
|
@@ -78,5 +78,7 @@ function faviconFilePath() {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
function shouldServeApplication(pathname) {
|
|
81
|
-
|
|
81
|
+
// Agent identities (for example employee.jefa) are route parameters, not assets.
|
|
82
|
+
const agentProfile = /^\/agents\/[^/]+\/[^/]+\/?$/.test(pathname);
|
|
83
|
+
return !pathname.startsWith('/api/') && (agentProfile || extname(pathname) === '');
|
|
82
84
|
}
|