@myagentroam/node 0.9.62 → 0.9.65
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/dist/capabilities.js +110 -2
- package/dist/claude-agent-sdk.js +330 -2
- package/dist/claude-channel.js +44 -2
- package/dist/codex-app-server.js +510 -2
- package/dist/config.js +106 -2
- package/dist/connector/node-connector-options.js +1 -2
- package/dist/connector/node-operation-router.js +22 -2
- package/dist/connector.js +1736 -2
- package/dist/control-outbound-scheduler.js +153 -2
- package/dist/database.js +639 -2
- package/dist/fake-runner.js +129 -2
- package/dist/git-askpass.js +12 -0
- package/dist/git-execution-helper.js +266 -0
- package/dist/health.js +3 -2
- package/dist/main.js +57 -2
- package/dist/mcp-installation-model.js +1 -2
- package/dist/migrations/v004.js +55 -3
- package/dist/migrations/v005.js +13 -3
- package/dist/migrations/v006.js +90 -3
- package/dist/migrations/v007.js +22 -3
- package/dist/migrations/v008.js +18 -3
- package/dist/migrations/v009.js +70 -4
- package/dist/migrations/v010.js +43 -3
- package/dist/migrations/v011.js +20 -3
- package/dist/migrations/v012.js +8 -0
- package/dist/native-jsonl-reader.js +186 -2
- package/dist/native-session-history.js +1143 -2
- package/dist/opencode-runtime.js +80 -2
- package/dist/opencode-server.js +259 -2
- package/dist/operational.js +122 -2
- package/dist/process-tree.js +18 -2
- package/dist/ripgrep-binary.js +31 -2
- package/dist/rotating-log.js +67 -2
- package/dist/runner/abstract-runner.js +260 -2
- package/dist/runner/claude/managed-run-controller.js +355 -2
- package/dist/runner/claude/tool-normalizer.js +120 -2
- package/dist/runner/claude-code-runner.js +303 -2
- package/dist/runner/codex/composer-image-markers.js +8 -2
- package/dist/runner/codex/conversation-parser.js +913 -2
- package/dist/runner/codex/managed-run-controller.js +494 -2
- package/dist/runner/codex/tool-normalizer.js +214 -2
- package/dist/runner/codex-runner.js +585 -2
- package/dist/runner/fake-test-runner.js +47 -2
- package/dist/runner/mar-agent/conversation-normalizer.js +644 -2
- package/dist/runner/mar-agent/executor-stdio-client-transport.js +139 -2
- package/dist/runner/mar-agent/generated-images.js +41 -2
- package/dist/runner/mar-agent/history-projection.js +106 -2
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +446 -2
- package/dist/runner/mar-agent/managed-run-controller.js +785 -2
- package/dist/runner/mar-agent/mcp-client-pool.js +233 -2
- package/dist/runner/mar-agent/runtime-model-factory.js +326 -2
- package/dist/runner/mar-agent-runner.js +458 -2
- package/dist/runner/opencode/conversation-parser.js +221 -2
- package/dist/runner/opencode/managed-run-controller.js +588 -2
- package/dist/runner/opencode/tool-normalizer.js +80 -2
- package/dist/runner/opencode-runner.js +426 -2
- package/dist/runner/runner-registry.js +87 -2
- package/dist/runner-command-engine.js +71 -2
- package/dist/runner-profiles.js +323 -2
- package/dist/runner-usage.js +190 -2
- package/dist/runtime-command-detector.js +73 -2
- package/dist/runtime-model.js +1 -2
- package/dist/runtime-state.js +1509 -2
- package/dist/service/attachment-domain-state.js +7 -2
- package/dist/service/bubblewrap-sandbox-executor.js +543 -2
- package/dist/service/continuous-run-coordinator.js +425 -2
- package/dist/service/conversation-history-service.js +552 -2
- package/dist/service/conversation-hosting-capability-service.js +61 -2
- package/dist/service/conversation-hosting-enablement-service.js +91 -2
- package/dist/service/conversation-hosting-maintenance-service.js +174 -2
- package/dist/service/conversation-segment-service.js +162 -2
- package/dist/service/conversation-template-node-service.js +992 -2
- package/dist/service/external-session-resume-service.js +152 -2
- package/dist/service/fake-managed-run-service.js +60 -2
- package/dist/service/git-execution-service.js +418 -0
- package/dist/service/git-operation-service.js +57 -0
- package/dist/service/hosted-conversation-node-service.js +1982 -2
- package/dist/service/hosted-conversation-store.js +1405 -2
- package/dist/service/managed-runner-session-service.js +35 -2
- package/dist/service/mar-agent-model-transport-test-service.js +88 -2
- package/dist/service/mcp-installation-verifier.js +87 -2
- package/dist/service/mcp-node-operation-service.js +90 -2
- package/dist/service/mission-mcp-server.js +124 -2
- package/dist/service/mission-mcp-stdio.js +40 -2
- package/dist/service/mission-runtime.js +142 -2
- package/dist/service/native-session-projection-service.js +274 -2
- package/dist/service/native-session-watch-service.js +683 -2
- package/dist/service/node-connection-lifecycle-service.js +88 -2
- package/dist/service/node-control-channel.js +85 -2
- package/dist/service/node-control-message-service.js +109 -2
- package/dist/service/node-request-service.js +62 -2
- package/dist/service/node-run-event-bridge.js +88 -2
- package/dist/service/node-terminal-channel.js +202 -2
- package/dist/service/node-upgrade-coordinator.js +137 -2
- package/dist/service/node-workspace-coordinator.js +97 -2
- package/dist/service/recent-run-projection-service.js +99 -2
- package/dist/service/run-attachment-service.js +321 -2
- package/dist/service/run-domain-state.js +4 -2
- package/dist/service/run-event-service.js +246 -2
- package/dist/service/run-ownership-policy.js +22 -2
- package/dist/service/run-workbench-service.js +120 -2
- package/dist/service/runner-channel-message-service.js +20 -2
- package/dist/service/runner-interaction-service.js +243 -2
- package/dist/service/runner-service.js +69 -2
- package/dist/service/runtime-credential-control-service.js +95 -2
- package/dist/service/session-catalog-service.js +404 -2
- package/dist/service/session-command-service.js +125 -2
- package/dist/service/session-context-service.js +128 -2
- package/dist/service/session-domain-state.js +9 -2
- package/dist/service/session-identity-service.js +86 -2
- package/dist/service/session-lifecycle-service.js +248 -2
- package/dist/service/session-message-service.js +270 -2
- package/dist/service/session-presentation-service.js +43 -2
- package/dist/service/session-query-service.js +82 -2
- package/dist/service/session-watch-slot-coordinator.js +21 -2
- package/dist/service/skill-directory-service.js +238 -2
- package/dist/service/skill-install-service.js +434 -2
- package/dist/service/skill-node-operation-service.js +206 -2
- package/dist/service/terminal-relay-state.js +3 -2
- package/dist/service/terminal-service.js +90 -2
- package/dist/service/web-search-error.js +40 -2
- package/dist/service/workbench-event-service.js +21 -2
- package/dist/service/workbench-manifest-service.js +69 -2
- package/dist/service/workspace-change-service.js +290 -2
- package/dist/service/workspace-content-search-service.js +457 -2
- package/dist/service/workspace-domain-state.js +41 -2
- package/dist/service/workspace-file-service.js +150 -2
- package/dist/service/workspace-git-exclude-service.js +119 -2
- package/dist/service/workspace-queue-workbench-service.js +176 -2
- package/dist/service/workspace-service.js +92 -2
- package/dist/service/workspace-session-service.js +154 -2
- package/dist/service/workspace-upload-service.js +262 -2
- package/dist/service/workspace-watch-controller.js +68 -2
- package/dist/service/workspace-watch-service.js +170 -2
- package/dist/service/workspace-watch-snapshot-service.js +218 -2
- package/dist/service/workspace-workbench-service.js +239 -2
- package/dist/service.js +91 -2
- package/dist/storage.js +32 -2
- package/dist/supervisor.js +136 -2
- package/dist/terminal.js +496 -2
- package/dist/util/git-credential-client.js +32 -0
- package/dist/util/node-operation-data.js +9 -2
- package/dist/util/node-operation-parsers.js +382 -2
- package/dist/util/personal-instructions.js +34 -2
- package/dist/util/runner-error.js +70 -2
- package/dist/util/runner-native-session-parsers.js +510 -2
- package/dist/util/safe-error.js +8 -2
- package/dist/util/secret-environment.js +25 -2
- package/dist/workspace.js +1793 -2
- package/package.json +11 -8
- package/vendor/ripgrep/darwin-arm64/rg +0 -0
- package/vendor/ripgrep/darwin-x64/rg +0 -0
- package/vendor/ripgrep/manifest.json +12 -0
- package/README.md +0 -42
- package/dist/event-buffer.js +0 -2
- package/dist/migrations/v001.js +0 -107
- package/dist/migrations/v002.js +0 -2
- package/dist/migrations/v003.js +0 -2
- package/dist/service/mcp-package-installer.js +0 -2
|
@@ -1,2 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { safeErrorCode } from '../util/safe-error.js';
|
|
2
|
+
import { probeBubblewrapSandbox } from './bubblewrap-sandbox-executor.js';
|
|
3
|
+
const CONVERSATION_HOSTING_API_VERSION = 1;
|
|
4
|
+
const SANDBOX_POLICY_VERSION = 1;
|
|
5
|
+
/** Runs the same bounded Bubblewrap policy used by hosted local processes. */
|
|
6
|
+
export async function probeBubblewrap() {
|
|
7
|
+
try {
|
|
8
|
+
const result = await probeBubblewrapSandbox();
|
|
9
|
+
return { available: true, version: result.version };
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return { available: false, reasonCode: 'SANDBOX_UNAVAILABLE' };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function conversationHostingCapability(capabilities, config, storageReady, storageFailureCode, probe = probeBubblewrap, capacity = 'AVAILABLE') {
|
|
16
|
+
if (capabilities.platform !== 'linux' || config?.conversationHosting?.enabled !== true) {
|
|
17
|
+
return disabledCapability();
|
|
18
|
+
}
|
|
19
|
+
if (!storageReady) {
|
|
20
|
+
return unavailableCapability(storageFailureCode ?? 'CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
21
|
+
}
|
|
22
|
+
if (capabilities.marAgent?.available !== true)
|
|
23
|
+
return unavailableCapability('RUNNER_UNAVAILABLE');
|
|
24
|
+
let sandbox;
|
|
25
|
+
try {
|
|
26
|
+
sandbox = await probe();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return unavailableCapability(safeErrorCode(error, 'SANDBOX_UNAVAILABLE'));
|
|
30
|
+
}
|
|
31
|
+
if (!sandbox.available)
|
|
32
|
+
return unavailableCapability(sandbox.reasonCode ?? 'SANDBOX_UNAVAILABLE');
|
|
33
|
+
return {
|
|
34
|
+
state: 'AVAILABLE',
|
|
35
|
+
apiVersion: CONVERSATION_HOSTING_API_VERSION,
|
|
36
|
+
sandboxBackend: 'BUBBLEWRAP',
|
|
37
|
+
sandboxPolicyVersion: SANDBOX_POLICY_VERSION,
|
|
38
|
+
acceptsNewConversations: capacity !== 'FULL',
|
|
39
|
+
supportsExistingConversations: true,
|
|
40
|
+
capacity
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function disabledCapability() {
|
|
44
|
+
return {
|
|
45
|
+
state: 'DISABLED',
|
|
46
|
+
apiVersion: CONVERSATION_HOSTING_API_VERSION,
|
|
47
|
+
acceptsNewConversations: false,
|
|
48
|
+
supportsExistingConversations: false,
|
|
49
|
+
capacity: 'AVAILABLE'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function unavailableCapability(reasonCode) {
|
|
53
|
+
return {
|
|
54
|
+
state: 'UNAVAILABLE',
|
|
55
|
+
apiVersion: CONVERSATION_HOSTING_API_VERSION,
|
|
56
|
+
acceptsNewConversations: false,
|
|
57
|
+
supportsExistingConversations: false,
|
|
58
|
+
capacity: 'AVAILABLE',
|
|
59
|
+
reasonCode
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -1,2 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { conversationHostingDisableInputSchema, conversationHostingEnableInputSchema, conversationHostingEnableResultSchema } from '@myagentroam/protocol';
|
|
2
|
+
import { saveNodeConfig } from '../config.js';
|
|
3
|
+
import { nodeOperationData } from '../util/node-operation-data.js';
|
|
4
|
+
/** Owns management operations that persist and hot-apply hosting configuration. */
|
|
5
|
+
export class ConversationHostingEnablementService {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
operations() {
|
|
11
|
+
return {
|
|
12
|
+
'conversation.hosting.enable': (data) => this.enable(data),
|
|
13
|
+
'conversation.hosting.disable': (data) => this.disable(data)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async disable(data) {
|
|
17
|
+
const parsed = conversationHostingDisableInputSchema.safeParse(nodeOperationData(data));
|
|
18
|
+
if (!parsed.success)
|
|
19
|
+
throw new Error('CONVERSATION_HOSTING_DISABLE_INPUT_INVALID');
|
|
20
|
+
if (this.options.platform() !== 'linux')
|
|
21
|
+
throw new Error('CONVERSATION_HOSTING_DISABLE_PLATFORM_UNSUPPORTED');
|
|
22
|
+
const current = this.options.config();
|
|
23
|
+
if (current === undefined)
|
|
24
|
+
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
25
|
+
if (current.conversationHosting?.enabled !== true)
|
|
26
|
+
return conversationHostingEnableResultSchema.parse({
|
|
27
|
+
capability: this.requireCapability()
|
|
28
|
+
});
|
|
29
|
+
const resume = this.options.prepareForDisable();
|
|
30
|
+
if (resume === undefined)
|
|
31
|
+
throw new Error('CONVERSATION_HOSTING_DISABLE_ACTIVE_RUNS');
|
|
32
|
+
const next = {
|
|
33
|
+
...current,
|
|
34
|
+
conversationHosting: {
|
|
35
|
+
...current.conversationHosting,
|
|
36
|
+
enabled: false
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
try {
|
|
40
|
+
await saveNodeConfig(next, this.options.configPath);
|
|
41
|
+
this.options.setConfig(next);
|
|
42
|
+
await this.options.initialize();
|
|
43
|
+
const result = conversationHostingEnableResultSchema.parse({
|
|
44
|
+
capability: this.requireCapability()
|
|
45
|
+
});
|
|
46
|
+
this.options.publishCapabilities();
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
resume();
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async enable(data) {
|
|
55
|
+
const parsed = conversationHostingEnableInputSchema.safeParse(nodeOperationData(data));
|
|
56
|
+
if (!parsed.success)
|
|
57
|
+
throw new Error('CONVERSATION_HOSTING_ENABLE_INPUT_INVALID');
|
|
58
|
+
if (this.options.platform() !== 'linux')
|
|
59
|
+
throw new Error('CONVERSATION_HOSTING_PLATFORM_UNSUPPORTED');
|
|
60
|
+
const current = this.options.config();
|
|
61
|
+
if (current === undefined)
|
|
62
|
+
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
63
|
+
if (current.conversationHosting?.enabled === true)
|
|
64
|
+
return conversationHostingEnableResultSchema.parse({
|
|
65
|
+
capability: this.requireCapability()
|
|
66
|
+
});
|
|
67
|
+
const next = {
|
|
68
|
+
...current,
|
|
69
|
+
conversationHosting: {
|
|
70
|
+
...current.conversationHosting,
|
|
71
|
+
enabled: true,
|
|
72
|
+
storeDirectory: current.conversationHosting?.storeDirectory ?? 'hosted-conversations',
|
|
73
|
+
inactiveRetentionDays: current.conversationHosting?.inactiveRetentionDays ?? 60
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
await saveNodeConfig(next, this.options.configPath);
|
|
77
|
+
this.options.setConfig(next);
|
|
78
|
+
await this.options.initialize();
|
|
79
|
+
const result = conversationHostingEnableResultSchema.parse({
|
|
80
|
+
capability: this.requireCapability()
|
|
81
|
+
});
|
|
82
|
+
this.options.publishCapabilities();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
requireCapability() {
|
|
86
|
+
const capability = this.options.capability();
|
|
87
|
+
if (capability === undefined)
|
|
88
|
+
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
89
|
+
return capability;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -1,2 +1,174 @@
|
|
|
1
|
-
|
|
2
|
-
const _0x579d04=_0x30c7;(function(_0x3b2111,_0xa26364){const _0x1175f5=_0x30c7,_0x405d82=_0x3b2111();while(!![]){try{const _0x15e4d5=parseInt(_0x1175f5(0x1d2))/(-0x4f8*-0x7+0x1ba7+0x3d*-0x106)*(parseInt(_0x1175f5(0x1d4))/(0x1*0x16a3+-0x1*0xf7+-0x15aa))+-parseInt(_0x1175f5(0x1bd))/(0x2105+0x233*-0xd+0x46b*-0x1)+-parseInt(_0x1175f5(0x1e8))/(-0x2c0+0x84c+-0x588)+-parseInt(_0x1175f5(0x1b7))/(0x1b14+0x5a5*0x1+-0x20b4)*(parseInt(_0x1175f5(0x1dd))/(0x1093*0x1+0x3*-0xc19+0x10a*0x13))+-parseInt(_0x1175f5(0x1ce))/(-0x1d95+0x35f+0x1a3d)*(-parseInt(_0x1175f5(0x1c2))/(-0x1f51*-0x1+0x174d*-0x1+-0x7fc))+parseInt(_0x1175f5(0x1ba))/(-0x6b*0x1+-0x1*-0x474+0x2*-0x200)*(-parseInt(_0x1175f5(0x1e0))/(-0xb2+-0x26a3+-0x275f*-0x1))+parseInt(_0x1175f5(0x1c8))/(-0x691+-0x1f84+-0x131*-0x20)*(parseInt(_0x1175f5(0x1e3))/(-0x1*0x17e3+-0x1065+0x2854));if(_0x15e4d5===_0xa26364)break;else _0x405d82['push'](_0x405d82['shift']());}catch(_0x39cddd){_0x405d82['push'](_0x405d82['shift']());}}}(_0x22ed,0xe0485+-0x12be5b+0x10088b));import{statfs}from'node:fs/promises';import{conversationHostingCleanupExecuteInputSchema,conversationHostingCleanupPreviewInputSchema,conversationHostingCleanupPreviewSchema,conversationHostingCleanupResultSchema,conversationHostingStorageCapacitySchema}from'@myagentroam/protocol';import{nodeOperationData}from'../util/node-operation-data.js';const DAY_MS=(-0x242d+-0x2*-0xc55+0xb9b)*(-0xfaa+-0x1*0x1e17+0x1*0x2dfd)*(0x2f*-0x71+-0x1*0xb89+0x1*0x2084)*(0x1831+-0x12d7+-0x172),AUTOMATIC_CLEANUP_INTERVAL_MS=DAY_MS;export class ConversationHostingMaintenanceService{[_0x579d04(0x1c4)];[_0x579d04(0x1d7)];[_0x579d04(0x1be)];[_0x579d04(0x1ad)];[_0x579d04(0x1d8)]=![];[_0x579d04(0x1de)]=Promise[_0x579d04(0x1b8)]();[_0x579d04(0x1b4)]=0x1899+0x2f*0x4d+-0x26bc;constructor(_0xa81096){const _0x738381=_0x579d04;this[_0x738381(0x1c4)]=_0xa81096,this[_0x738381(0x1d7)]=_0xa81096[_0x738381(0x1d7)]??Date[_0x738381(0x1d7)],this[_0x738381(0x1be)]=_0xa81096[_0x738381(0x1be)]??(()=>filesystemCapacity(_0xa81096[_0x738381(0x1bf)][_0x738381(0x1c0)]));}[_0x579d04(0x1c1)](){const _0x46d01f=_0x579d04;return{'conversation.hosting.storage.inspect':()=>this[_0x46d01f(0x1b1)]()[_0x46d01f(0x1ae)](_0x26468d=>({'storage':_0x26468d})),'conversation.hosting.cleanup.preview':_0x35f9b5=>this[_0x46d01f(0x1da)](conversationHostingCleanupPreviewInputSchema[_0x46d01f(0x1a9)](nodeOperationData(_0x35f9b5))[_0x46d01f(0x1d3)])[_0x46d01f(0x1ae)](_0x7ac2d7=>({'preview':_0x7ac2d7})),'conversation.hosting.cleanup.execute':_0x44d06d=>this[_0x46d01f(0x1ab)](()=>this[_0x46d01f(0x1bc)](conversationHostingCleanupExecuteInputSchema[_0x46d01f(0x1a9)](nodeOperationData(_0x44d06d))[_0x46d01f(0x1d3)]))[_0x46d01f(0x1ae)](_0x4d5192=>({'result':_0x4d5192}))};}async[_0x579d04(0x1b1)](){const _0x5a26bc=_0x579d04,_0x4f6c8a=this[_0x5a26bc(0x1d7)](),_0x229b4c=await this[_0x5a26bc(0x1be)]();assertFilesystemCapacity(_0x229b4c);const _0x2f4618=_0x229b4c[_0x5a26bc(0x1cc)]-_0x229b4c[_0x5a26bc(0x1d5)],_0x38ceff=Number(BigInt(_0x2f4618)*0x2710n/BigInt(_0x229b4c[_0x5a26bc(0x1cc)])),_0x4addc3=_0x38ceff>=-0xb*0x51b+-0x4*-0x6f1+0x4181?_0x5a26bc(0x1ac):_0x38ceff>=-0x168b*-0x1+-0x77*-0x45+-0x156a*0x1?_0x5a26bc(0x1df):_0x5a26bc(0x1e4);return conversationHostingStorageCapacitySchema[_0x5a26bc(0x1a9)]({'hostedStorageUsedBytes':await this[_0x5a26bc(0x1c4)][_0x5a26bc(0x1bf)][_0x5a26bc(0x1e1)](),'filesystemTotalBytes':_0x229b4c[_0x5a26bc(0x1cc)],'filesystemAvailableBytes':_0x229b4c[_0x5a26bc(0x1d5)],'filesystemUsageBasisPoints':_0x38ceff,'capacity':_0x4addc3,'acceptsNewConversations':_0x4addc3!==_0x5a26bc(0x1ac),'automaticCleanupInactiveDays':this[_0x5a26bc(0x1c4)][_0x5a26bc(0x1bb)],'measuredAt':_0x4f6c8a});}async[_0x579d04(0x1c7)](){const _0x53bbed=_0x579d04;if((await this[_0x53bbed(0x1b1)]())[_0x53bbed(0x1c9)]===_0x53bbed(0x1ac))throw new Error(_0x53bbed(0x1e2));}async[_0x579d04(0x1ea)](){const _0x581146=_0x579d04;await this[_0x581146(0x1ab)](()=>this[_0x581146(0x1bc)](this[_0x581146(0x1c4)][_0x581146(0x1bb)]));}async[_0x579d04(0x1b0)](){const _0x5479df=_0x579d04;if(this[_0x5479df(0x1d8)])return;this[_0x5479df(0x1d8)]=!![],await this[_0x5479df(0x1ea)]()[_0x5479df(0x1b6)](_0x5540a7=>this[_0x5479df(0x1c4)][_0x5479df(0x1b2)]?.(_0x5540a7));if(!this[_0x5479df(0x1d8)])return;this[_0x5479df(0x1ad)]=setInterval(()=>{const _0x45ad60=_0x5479df;this[_0x45ad60(0x1ca)]();},AUTOMATIC_CLEANUP_INTERVAL_MS),this[_0x5479df(0x1ad)][_0x5479df(0x1d1)]();}[_0x579d04(0x1e9)](){const _0x7988b9=_0x579d04;this[_0x7988b9(0x1d8)]=![];if(this[_0x7988b9(0x1ad)]!==undefined)clearInterval(this[_0x7988b9(0x1ad)]);return this[_0x7988b9(0x1ad)]=undefined,this[_0x7988b9(0x1b4)]===0x76f+-0x378*-0x2+0x1*-0xe5f?undefined:this[_0x7988b9(0x1de)];}[_0x579d04(0x1ca)](){const _0x2798cc=_0x579d04;void this[_0x2798cc(0x1ea)]()[_0x2798cc(0x1b6)](_0x267db6=>this[_0x2798cc(0x1c4)][_0x2798cc(0x1b2)]?.(_0x267db6));}[_0x579d04(0x1ab)](_0x25e8b5){const _0x4881ea=_0x579d04;this[_0x4881ea(0x1b4)]+=-0x2638*-0x1+0x371*0x2+-0x2d19;const _0x13cf70=this[_0x4881ea(0x1de)][_0x4881ea(0x1ae)](_0x25e8b5,_0x25e8b5);return this[_0x4881ea(0x1de)]=_0x13cf70[_0x4881ea(0x1ae)](()=>{const _0x4685c1=_0x4881ea;this[_0x4685c1(0x1b4)]-=0x2*0x775+-0x8ab+0x11*-0x5e;},()=>{const _0x4b3dc6=_0x4881ea;this[_0x4b3dc6(0x1b4)]-=0x13*0x167+0x314+-0x18*0x13d;}),_0x13cf70;}async[_0x579d04(0x1da)](_0x40a5c8){const _0x49cb7c=_0x579d04,_0x1a1f77=this[_0x49cb7c(0x1d7)](),_0x290af1=cutoff(_0x1a1f77,_0x40a5c8),_0x3e8b6c=this[_0x49cb7c(0x1c4)][_0x49cb7c(0x1bf)][_0x49cb7c(0x1b3)](_0x290af1)[_0x49cb7c(0x1cf)](_0x147ae5=>!this[_0x49cb7c(0x1c4)][_0x49cb7c(0x1d6)](_0x147ae5['id']));let _0x54bf8b=-0x1c91+-0x11*0x187+0x3688*0x1;for(const _0x2cc8ab of _0x3e8b6c)_0x54bf8b+=await this[_0x49cb7c(0x1c4)][_0x49cb7c(0x1bf)][_0x49cb7c(0x1cb)](_0x2cc8ab);return conversationHostingCleanupPreviewSchema[_0x49cb7c(0x1a9)]({'inactiveDays':_0x40a5c8,'cutoffAt':_0x290af1,'candidateConversationCount':_0x3e8b6c[_0x49cb7c(0x1e7)],'reclaimableBytes':_0x54bf8b,'measuredAt':_0x1a1f77});}async[_0x579d04(0x1bc)](_0x3f9930){const _0x13fe47=_0x579d04,_0x13c122=this[_0x13fe47(0x1d7)](),_0x3bec7e=cutoff(_0x13c122,_0x3f9930);let _0x1d58d3=-0x3d0*0x2+-0xa30*-0x1+-0x290,_0x461c8d=-0x7f*-0x20+-0x17*0x66+0x2*-0x35b,_0x5de868=0x32*0x48+-0x8a3+-0x56d;for(const _0x11e1b1 of this[_0x13fe47(0x1c4)][_0x13fe47(0x1bf)][_0x13fe47(0x1b3)](_0x3bec7e)){const _0x8b5c50=this[_0x13fe47(0x1c4)][_0x13fe47(0x1dc)](_0x11e1b1['id']);if(_0x8b5c50===undefined){_0x5de868+=-0x2180*0x1+-0x2ce*0x7+0x3523;continue;}try{const _0x301cad=this[_0x13fe47(0x1c4)][_0x13fe47(0x1bf)][_0x13fe47(0x1af)](_0x11e1b1['id']);if(_0x301cad===undefined||_0x301cad[_0x13fe47(0x1e6)]>=_0x3bec7e||_0x301cad[_0x13fe47(0x1d9)]!==_0x13fe47(0x1d0)){_0x5de868+=0xdb1+-0x1bfe+0xe4e;continue;}await this[_0x13fe47(0x1c4)][_0x13fe47(0x1c3)](_0x301cad);const _0x30c103=await this[_0x13fe47(0x1c4)][_0x13fe47(0x1bf)][_0x13fe47(0x1db)](_0x301cad['id']);_0x1d58d3+=0x5d*0xe+0x2*0xa57+-0x527*0x5,_0x461c8d+=_0x30c103[_0x13fe47(0x1aa)];}finally{_0x8b5c50();}}const _0x4cd263=this[_0x13fe47(0x1d7)]();return conversationHostingCleanupResultSchema[_0x13fe47(0x1a9)]({'inactiveDays':_0x3f9930,'cutoffAt':_0x3bec7e,'deletedConversationCount':_0x1d58d3,'releasedBytes':_0x461c8d,'skippedConversationCount':_0x5de868,'completedAt':_0x4cd263});}}function cutoff(_0x1433c2,_0x59074f){const _0x135e0f=_0x579d04;return Math[_0x135e0f(0x1b9)](-0xb4f+-0x10fe+-0x7*-0x40b,_0x1433c2-_0x59074f*DAY_MS);}function _0x30c7(_0x266163,_0x3c1f8d){_0x266163=_0x266163-(-0x1602+-0x1*-0xc1+0x16e9);const _0x552ea7=_0x22ed();let _0x5459e9=_0x552ea7[_0x266163];if(_0x30c7['AjBYBV']===undefined){var _0x5a9cbb=function(_0x43102b){const _0x24808b='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x525eaf='',_0x161ab8='';for(let _0x5151c3=0x129e*-0x2+-0x14c2+-0x1*-0x39fe,_0x1efbc2,_0x410298,_0x41091f=0xd58+-0x37d+-0x9db;_0x410298=_0x43102b['charAt'](_0x41091f++);~_0x410298&&(_0x1efbc2=_0x5151c3%(-0xf25+0x3*0xa4a+-0xfb5)?_0x1efbc2*(0x1*-0xb89+0x1651+0x2*-0x544)+_0x410298:_0x410298,_0x5151c3++%(0x1831+-0x12d7+-0x556))?_0x525eaf+=String['fromCharCode'](0x1899+0x2f*0x4d+-0x25bd&_0x1efbc2>>(-(-0x2f*0xa1+-0x2b*-0x57+0xef4)*_0x5151c3&-0x2a7*-0x5+-0x69*-0x2e+-0x201b*0x1)):0x76f+-0x378*-0x2+0x1*-0xe5f){_0x410298=_0x24808b['indexOf'](_0x410298);}for(let _0x40d9b0=-0x2638*-0x1+0x371*0x2+-0x2d1a,_0x2525ca=_0x525eaf['length'];_0x40d9b0<_0x2525ca;_0x40d9b0++){_0x161ab8+='%'+('00'+_0x525eaf['charCodeAt'](_0x40d9b0)['toString'](0x2*0x775+-0x8ab+0x1*-0x62f))['slice'](-(0x13*0x167+0x314+-0x1*0x1db7));}return decodeURIComponent(_0x161ab8);};_0x30c7['jFzSlJ']=_0x5a9cbb,_0x30c7['ZtwWXY']={},_0x30c7['AjBYBV']=!![];}const _0x436945=_0x552ea7[-0x1c91+-0x11*0x187+0x3688*0x1];_0x30c7['RQoomS']!==_0x436945&&(_0x30c7['ZtwWXY']={},_0x30c7['RQoomS']=_0x436945);const _0x2d9ef7=_0x30c7['ZtwWXY'][_0x266163];return _0x2d9ef7===undefined?(_0x5459e9=_0x30c7['jFzSlJ'](_0x5459e9),_0x30c7['ZtwWXY'][_0x266163]=_0x5459e9):_0x5459e9=_0x2d9ef7,_0x5459e9;}async function filesystemCapacity(_0x2c0009){const _0x5a3d7a=_0x579d04,_0x5603c7=await statfs(_0x2c0009);return{'totalBytes':safeFilesystemBytes(_0x5603c7[_0x5a3d7a(0x1b5)],_0x5603c7[_0x5a3d7a(0x1e5)]),'availableBytes':safeFilesystemBytes(_0x5603c7[_0x5a3d7a(0x1b5)],_0x5603c7[_0x5a3d7a(0x1c5)],!![])};}function safeFilesystemBytes(_0x21ed38,_0x38bcb2,_0x43ce5f=![]){const _0x4e3990=_0x579d04;if(!Number[_0x4e3990(0x1cd)](_0x21ed38)||_0x21ed38<=-0x29*0x8f+0x7*0xd6+0x110d||!Number[_0x4e3990(0x1cd)](_0x38bcb2)||_0x38bcb2<0x5*-0xb9+0x1cda+-0x193d)throw new Error(_0x4e3990(0x1c6));const _0x2cc84c=BigInt(_0x21ed38)*BigInt(_0x38bcb2);if(!_0x43ce5f&&_0x2cc84c===0x0n||_0x2cc84c>BigInt(Number[_0x4e3990(0x1a8)]))throw new Error(_0x4e3990(0x1c6));return Number(_0x2cc84c);}function _0x22ed(){const _0x7e0e2e=['ndG0ntu2mgv0shfoqW','CMvSzwfZzunVBNzLCNnHDgLVBG','B3b0Aw9UCW','yMf2ywLS','q09ovKvsu0fusu9ox0Hpu1rjtKDFu1rpuKfhrv9vtKfwquLmqujmrq','yxnZzxj0q2fUq3jLyxrL','ntaXmJeXn094reHuCG','y2fWywnPDhK','DhjPz2DLCKf1Dg9TyxrPy0nSzwfUDxa','y29UDMvYC2f0Aw9Uu3rVCMfNzuj5DgvZ','Dg90ywXcExrLCW','AxntywzLsw50zwDLCG','n2PeBgLuAW','zMLSDgvY','qunusvzf','Dw5Yzwy','mtq2nJK5mwPNzvfbEG','Aw5Hy3rPDMveyxLZ','mNPnuhzeva','yxzHAwXHyMXLqNL0zxm','AxndB252zxjZyxrPB25jBLvZzq','BM93','yxv0B21HDgLJq2XLyw51Cfn0yxj0zwq','C3rHDhvZ','ChjLDMLLDW','CgvYBwfUzw50rgvSzxrLq29UDMvYC2f0Aw9U','yMvNAw5dB252zxjZyxrPB25dBgvHBNvW','mJGYnZe0meziqMfhBW','y2XLyw51CfrHAwW','seLhsa','mtbZChnhC3O','Ag9ZDgvKu3rVCMfNzvvZzwrcExrLCW','q09ovKvsu0fusu9ox0Hpu1rjtKDFu1rpuKfhrv9gvuXm','mZz3y0zNzvO','qvzbsuXbqKXf','yMXVy2TZ','BgfZDefJDgL2Axr5qxq','BgvUz3rO','mJqWndG2ohfev3jiua','zgLZCg9Zzq','CNvUqxv0B21HDgLJq2XLyw51Ca','tufyx1nbrKvFsu5uruDfuG','CgfYC2u','CMvSzwfZzwrcExrLCW','CNvUq2XLyw51Cev4y2X1C2L2zq','rLvmta','yxv0B21HDgLJq2XLyw51CfrPBwvY','DgHLBG','zMLUzenVBNzLCNnHDgLVBKj5swq','C3rHCNq','C3rVCMfNzunHCgfJAxr5','B25bDxrVBwf0AwndBgvHBNvWrxjYB3i','Aw5Hy3rPDMvdBgvHBNvWq2fUzgLKyxrLCW','CgvUzgLUz0nSzwfUDxbdB3vUDa','yNnPEMu','y2f0y2G','nwvYqvj4vq','CMvZB2X2zq','Bwf4','oteYmdqZogLbEKH3BG','Aw5Hy3rPDMvszxrLBNrPB25eyxLZ','zxHLy3v0zq','mtGZodm3ovjMqxjbvq','zMLSzxn5C3rLBunHCgfJAxr5','C3rVCMu','CM9VDerPCMvJDg9YEq','B3bLCMf0Aw9UCW'];_0x22ed=function(){return _0x7e0e2e;};return _0x22ed();}function assertFilesystemCapacity(_0x282927){const _0x2e65f7=_0x579d04;if(!Number[_0x2e65f7(0x1cd)](_0x282927[_0x2e65f7(0x1cc)])||_0x282927[_0x2e65f7(0x1cc)]<=-0x518*-0x2+0xe52+-0xc41*0x2||!Number[_0x2e65f7(0x1cd)](_0x282927[_0x2e65f7(0x1d5)])||_0x282927[_0x2e65f7(0x1d5)]<0x10f*0x2+-0x7ee+-0x2e8*-0x2||_0x282927[_0x2e65f7(0x1d5)]>_0x282927[_0x2e65f7(0x1cc)])throw new Error(_0x2e65f7(0x1c6));}
|
|
1
|
+
import { statfs } from 'node:fs/promises';
|
|
2
|
+
import { conversationHostingCleanupExecuteInputSchema, conversationHostingCleanupPreviewInputSchema, conversationHostingCleanupPreviewSchema, conversationHostingCleanupResultSchema, conversationHostingStorageCapacitySchema } from '@myagentroam/protocol';
|
|
3
|
+
import { nodeOperationData } from '../util/node-operation-data.js';
|
|
4
|
+
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
5
|
+
const AUTOMATIC_CLEANUP_INTERVAL_MS = DAY_MS;
|
|
6
|
+
/** Owns aggregate storage watermarks and permanent hosted-conversation cleanup. */
|
|
7
|
+
export class ConversationHostingMaintenanceService {
|
|
8
|
+
options;
|
|
9
|
+
now;
|
|
10
|
+
filesystemCapacity;
|
|
11
|
+
automaticCleanupTimer;
|
|
12
|
+
automaticCleanupStarted = false;
|
|
13
|
+
cleanupTail = Promise.resolve();
|
|
14
|
+
pendingCleanupCount = 0;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.now = options.now ?? Date.now;
|
|
18
|
+
this.filesystemCapacity =
|
|
19
|
+
options.filesystemCapacity ?? (() => filesystemCapacity(options.store.rootDirectory));
|
|
20
|
+
}
|
|
21
|
+
operations() {
|
|
22
|
+
return {
|
|
23
|
+
'conversation.hosting.storage.inspect': () => this.storageCapacity().then((storage) => ({ storage })),
|
|
24
|
+
'conversation.hosting.cleanup.preview': (raw) => this.preview(conversationHostingCleanupPreviewInputSchema.parse(nodeOperationData(raw)).inactiveDays).then((preview) => ({ preview })),
|
|
25
|
+
'conversation.hosting.cleanup.execute': (raw) => this.runCleanupExclusive(() => this.execute(conversationHostingCleanupExecuteInputSchema.parse(nodeOperationData(raw)).inactiveDays)).then((result) => ({ result }))
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async storageCapacity() {
|
|
29
|
+
const measuredAt = this.now();
|
|
30
|
+
const filesystem = await this.filesystemCapacity();
|
|
31
|
+
assertFilesystemCapacity(filesystem);
|
|
32
|
+
const usedBytes = filesystem.totalBytes - filesystem.availableBytes;
|
|
33
|
+
const filesystemUsageBasisPoints = Number((BigInt(usedBytes) * 10000n) / BigInt(filesystem.totalBytes));
|
|
34
|
+
const capacity = filesystemUsageBasisPoints >= 9_500
|
|
35
|
+
? 'FULL'
|
|
36
|
+
: filesystemUsageBasisPoints >= 8_500
|
|
37
|
+
? 'HIGH'
|
|
38
|
+
: 'AVAILABLE';
|
|
39
|
+
return conversationHostingStorageCapacitySchema.parse({
|
|
40
|
+
hostedStorageUsedBytes: await this.options.store.hostedStorageUsedBytes(),
|
|
41
|
+
filesystemTotalBytes: filesystem.totalBytes,
|
|
42
|
+
filesystemAvailableBytes: filesystem.availableBytes,
|
|
43
|
+
filesystemUsageBasisPoints,
|
|
44
|
+
capacity,
|
|
45
|
+
acceptsNewConversations: capacity !== 'FULL',
|
|
46
|
+
automaticCleanupInactiveDays: this.options.inactiveRetentionDays,
|
|
47
|
+
measuredAt
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async assertCanCreate() {
|
|
51
|
+
if ((await this.storageCapacity()).capacity === 'FULL')
|
|
52
|
+
throw new Error('CONVERSATION_HOSTING_STORAGE_FULL');
|
|
53
|
+
}
|
|
54
|
+
async runAutomaticCleanup() {
|
|
55
|
+
await this.runCleanupExclusive(() => this.execute(this.options.inactiveRetentionDays));
|
|
56
|
+
}
|
|
57
|
+
async start() {
|
|
58
|
+
if (this.automaticCleanupStarted)
|
|
59
|
+
return;
|
|
60
|
+
this.automaticCleanupStarted = true;
|
|
61
|
+
await this.runAutomaticCleanup().catch((error) => this.options.onAutomaticCleanupError?.(error));
|
|
62
|
+
if (!this.automaticCleanupStarted)
|
|
63
|
+
return;
|
|
64
|
+
this.automaticCleanupTimer = setInterval(() => {
|
|
65
|
+
this.triggerAutomaticCleanup();
|
|
66
|
+
}, AUTOMATIC_CLEANUP_INTERVAL_MS);
|
|
67
|
+
this.automaticCleanupTimer.unref();
|
|
68
|
+
}
|
|
69
|
+
dispose() {
|
|
70
|
+
this.automaticCleanupStarted = false;
|
|
71
|
+
if (this.automaticCleanupTimer !== undefined)
|
|
72
|
+
clearInterval(this.automaticCleanupTimer);
|
|
73
|
+
this.automaticCleanupTimer = undefined;
|
|
74
|
+
return this.pendingCleanupCount === 0 ? undefined : this.cleanupTail;
|
|
75
|
+
}
|
|
76
|
+
triggerAutomaticCleanup() {
|
|
77
|
+
void this.runAutomaticCleanup().catch((error) => this.options.onAutomaticCleanupError?.(error));
|
|
78
|
+
}
|
|
79
|
+
runCleanupExclusive(operation) {
|
|
80
|
+
this.pendingCleanupCount += 1;
|
|
81
|
+
const result = this.cleanupTail.then(operation, operation);
|
|
82
|
+
this.cleanupTail = result.then(() => {
|
|
83
|
+
this.pendingCleanupCount -= 1;
|
|
84
|
+
}, () => {
|
|
85
|
+
this.pendingCleanupCount -= 1;
|
|
86
|
+
});
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
async preview(inactiveDays) {
|
|
90
|
+
const measuredAt = this.now();
|
|
91
|
+
const cutoffAt = cutoff(measuredAt, inactiveDays);
|
|
92
|
+
const available = this.options.store
|
|
93
|
+
.inactiveCleanupCandidates(cutoffAt)
|
|
94
|
+
.filter((conversation) => !this.options.isConversationInUse(conversation.id));
|
|
95
|
+
let reclaimableBytes = 0;
|
|
96
|
+
for (const conversation of available)
|
|
97
|
+
reclaimableBytes += await this.options.store.conversationStorageBytes(conversation);
|
|
98
|
+
return conversationHostingCleanupPreviewSchema.parse({
|
|
99
|
+
inactiveDays,
|
|
100
|
+
cutoffAt,
|
|
101
|
+
candidateConversationCount: available.length,
|
|
102
|
+
reclaimableBytes,
|
|
103
|
+
measuredAt
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async execute(inactiveDays) {
|
|
107
|
+
const startedAt = this.now();
|
|
108
|
+
const cutoffAt = cutoff(startedAt, inactiveDays);
|
|
109
|
+
let deletedConversationCount = 0;
|
|
110
|
+
let releasedBytes = 0;
|
|
111
|
+
let skippedConversationCount = 0;
|
|
112
|
+
for (const candidate of this.options.store.inactiveCleanupCandidates(cutoffAt)) {
|
|
113
|
+
const finishCleanup = this.options.beginConversationCleanup(candidate.id);
|
|
114
|
+
if (finishCleanup === undefined) {
|
|
115
|
+
skippedConversationCount += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const current = this.options.store.findConversationById(candidate.id);
|
|
120
|
+
if (current === undefined ||
|
|
121
|
+
current.lastActivityAt >= cutoffAt ||
|
|
122
|
+
current.status !== 'ACTIVE') {
|
|
123
|
+
skippedConversationCount += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
await this.options.releaseConversation(current);
|
|
127
|
+
const deleted = await this.options.store.permanentDeleteConversation(current.id);
|
|
128
|
+
deletedConversationCount += 1;
|
|
129
|
+
releasedBytes += deleted.releasedBytes;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
finishCleanup();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const completedAt = this.now();
|
|
136
|
+
return conversationHostingCleanupResultSchema.parse({
|
|
137
|
+
inactiveDays,
|
|
138
|
+
cutoffAt,
|
|
139
|
+
deletedConversationCount,
|
|
140
|
+
releasedBytes,
|
|
141
|
+
skippedConversationCount,
|
|
142
|
+
completedAt
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function cutoff(now, inactiveDays) {
|
|
147
|
+
return Math.max(0, now - inactiveDays * DAY_MS);
|
|
148
|
+
}
|
|
149
|
+
async function filesystemCapacity(rootDirectory) {
|
|
150
|
+
const value = await statfs(rootDirectory);
|
|
151
|
+
return {
|
|
152
|
+
totalBytes: safeFilesystemBytes(value.bsize, value.blocks),
|
|
153
|
+
availableBytes: safeFilesystemBytes(value.bsize, value.bavail, true)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function safeFilesystemBytes(blockSize, blocks, allowZero = false) {
|
|
157
|
+
if (!Number.isSafeInteger(blockSize) ||
|
|
158
|
+
blockSize <= 0 ||
|
|
159
|
+
!Number.isSafeInteger(blocks) ||
|
|
160
|
+
blocks < 0)
|
|
161
|
+
throw new Error('CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
162
|
+
const bytes = BigInt(blockSize) * BigInt(blocks);
|
|
163
|
+
if ((!allowZero && bytes === 0n) || bytes > BigInt(Number.MAX_SAFE_INTEGER))
|
|
164
|
+
throw new Error('CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
165
|
+
return Number(bytes);
|
|
166
|
+
}
|
|
167
|
+
function assertFilesystemCapacity(value) {
|
|
168
|
+
if (!Number.isSafeInteger(value.totalBytes) ||
|
|
169
|
+
value.totalBytes <= 0 ||
|
|
170
|
+
!Number.isSafeInteger(value.availableBytes) ||
|
|
171
|
+
value.availableBytes < 0 ||
|
|
172
|
+
value.availableBytes > value.totalBytes)
|
|
173
|
+
throw new Error('CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
174
|
+
}
|
|
@@ -1,2 +1,162 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const TURN_PAGE_SIZE = 30;
|
|
2
|
+
export function conversationSegments(turn) {
|
|
3
|
+
const groups = [];
|
|
4
|
+
let current = [];
|
|
5
|
+
for (const item of turn.items) {
|
|
6
|
+
if (current.at(-1)?.kind === 'assistant_message' && item.kind !== 'assistant_message') {
|
|
7
|
+
groups.push(current);
|
|
8
|
+
current = [];
|
|
9
|
+
}
|
|
10
|
+
current.push(item);
|
|
11
|
+
}
|
|
12
|
+
if (current.length > 0 || groups.length === 0)
|
|
13
|
+
groups.push(current);
|
|
14
|
+
return groups.map((items, index) => {
|
|
15
|
+
const id = `${turn.id}:segment:${index}`;
|
|
16
|
+
const latest = index === groups.length - 1;
|
|
17
|
+
const projectedItems = items.map((item) => ({
|
|
18
|
+
...item,
|
|
19
|
+
runId: turn.runId,
|
|
20
|
+
segmentId: id,
|
|
21
|
+
segmentIndex: index
|
|
22
|
+
}));
|
|
23
|
+
return {
|
|
24
|
+
id,
|
|
25
|
+
sessionId: turn.sessionId,
|
|
26
|
+
turnId: turn.id,
|
|
27
|
+
runId: turn.runId,
|
|
28
|
+
index,
|
|
29
|
+
status: (latest ? turn.status : 'SUCCEEDED'),
|
|
30
|
+
...(turn.rewind === undefined ? {} : { rewind: turn.rewind }),
|
|
31
|
+
items: projectedItems,
|
|
32
|
+
startedAt: projectedItems[0]?.startedAt ?? turn.startedAt,
|
|
33
|
+
completedAt: projectedItems.at(-1)?.completedAt ?? null
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function conversationSegmentIdentityForItem(turn, itemId, itemKind) {
|
|
38
|
+
let index = 0;
|
|
39
|
+
const runtimeItemId = turn.runId === null ? itemId : `${turn.runId}:${itemId}`;
|
|
40
|
+
for (const [itemIndex, item] of turn.items.entries()) {
|
|
41
|
+
const previous = turn.items[itemIndex - 1];
|
|
42
|
+
if (previous?.kind === 'assistant_message' && item.kind !== 'assistant_message')
|
|
43
|
+
index += 1;
|
|
44
|
+
if (item.id === itemId || item.id === runtimeItemId)
|
|
45
|
+
return { id: `${turn.id}:segment:${index}`, index };
|
|
46
|
+
}
|
|
47
|
+
if (turn.items.at(-1)?.kind === 'assistant_message' && itemKind !== 'assistant_message')
|
|
48
|
+
index += 1;
|
|
49
|
+
return { id: `${turn.id}:segment:${index}`, index };
|
|
50
|
+
}
|
|
51
|
+
export class ConversationSegmentService {
|
|
52
|
+
readTurns;
|
|
53
|
+
constructor(readTurns) {
|
|
54
|
+
this.readTurns = readTurns;
|
|
55
|
+
}
|
|
56
|
+
projectInitial(session, page, limit) {
|
|
57
|
+
const all = page.turns.flatMap(conversationSegments);
|
|
58
|
+
const segments = all.slice(-limit);
|
|
59
|
+
return {
|
|
60
|
+
snapshotSequence: page.snapshotSequence,
|
|
61
|
+
source: page.source,
|
|
62
|
+
readAt: page.readAt,
|
|
63
|
+
latestVisibleAt: page.latestVisibleAt,
|
|
64
|
+
unit: 'SEGMENT',
|
|
65
|
+
segments,
|
|
66
|
+
nextCursor: segments.length === 0 || (segments.length === all.length && page.nextCursor === null)
|
|
67
|
+
? null
|
|
68
|
+
: encodeCursor({
|
|
69
|
+
version: 1,
|
|
70
|
+
sessionId: session.id,
|
|
71
|
+
beforeSegmentId: segments[0].id
|
|
72
|
+
})
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async read(session, input) {
|
|
76
|
+
const limit = Math.min(Math.max(input.limit ?? 30, 1), 500);
|
|
77
|
+
const boundary = input.cursor === undefined ? undefined : decodeCursor(input.cursor, session.id);
|
|
78
|
+
const collected = [];
|
|
79
|
+
const visited = new Set();
|
|
80
|
+
let turnCursor;
|
|
81
|
+
let locatingBoundary = boundary !== undefined;
|
|
82
|
+
let hasOlder = false;
|
|
83
|
+
let metadata;
|
|
84
|
+
while (collected.length < limit) {
|
|
85
|
+
const page = await this.readTurns(session, {
|
|
86
|
+
...(turnCursor === undefined ? {} : { cursor: turnCursor }),
|
|
87
|
+
limit: TURN_PAGE_SIZE
|
|
88
|
+
});
|
|
89
|
+
metadata ??= {
|
|
90
|
+
snapshotSequence: page.snapshotSequence,
|
|
91
|
+
source: page.source,
|
|
92
|
+
readAt: page.readAt,
|
|
93
|
+
latestVisibleAt: page.latestVisibleAt,
|
|
94
|
+
...(boundary === undefined && page.deferredOlderHistory === true
|
|
95
|
+
? { deferredOlderHistory: true }
|
|
96
|
+
: {})
|
|
97
|
+
};
|
|
98
|
+
const pageSegments = page.turns.flatMap(conversationSegments);
|
|
99
|
+
let eligible = pageSegments;
|
|
100
|
+
if (locatingBoundary) {
|
|
101
|
+
const boundaryIndex = pageSegments.findIndex((segment) => segment.id === boundary?.beforeSegmentId);
|
|
102
|
+
if (boundaryIndex >= 0) {
|
|
103
|
+
eligible = pageSegments.slice(0, boundaryIndex);
|
|
104
|
+
locatingBoundary = false;
|
|
105
|
+
}
|
|
106
|
+
else
|
|
107
|
+
eligible = [];
|
|
108
|
+
}
|
|
109
|
+
if (!locatingBoundary && eligible.length > 0) {
|
|
110
|
+
const remaining = Math.max(0, limit - collected.length);
|
|
111
|
+
const take = eligible.slice(-remaining);
|
|
112
|
+
hasOlder = eligible.length > take.length || page.nextCursor !== null;
|
|
113
|
+
collected.unshift(...take);
|
|
114
|
+
}
|
|
115
|
+
if (boundary === undefined && page.deferredOlderHistory === true)
|
|
116
|
+
break;
|
|
117
|
+
if (collected.length >= limit || page.nextCursor === null)
|
|
118
|
+
break;
|
|
119
|
+
if (visited.has(page.nextCursor))
|
|
120
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
121
|
+
visited.add(page.nextCursor);
|
|
122
|
+
turnCursor = page.nextCursor;
|
|
123
|
+
}
|
|
124
|
+
if (locatingBoundary)
|
|
125
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
126
|
+
const nextCursor = collected.length === 0 || !hasOlder
|
|
127
|
+
? null
|
|
128
|
+
: encodeCursor({
|
|
129
|
+
version: 1,
|
|
130
|
+
sessionId: session.id,
|
|
131
|
+
beforeSegmentId: collected[0].id
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
...(metadata ?? {
|
|
135
|
+
snapshotSequence: 0,
|
|
136
|
+
source: 'runtime',
|
|
137
|
+
readAt: Date.now(),
|
|
138
|
+
latestVisibleAt: 0
|
|
139
|
+
}),
|
|
140
|
+
unit: 'SEGMENT',
|
|
141
|
+
segments: collected,
|
|
142
|
+
nextCursor
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function encodeCursor(cursor) {
|
|
147
|
+
return Buffer.from(JSON.stringify(cursor)).toString('base64url');
|
|
148
|
+
}
|
|
149
|
+
function decodeCursor(value, sessionId) {
|
|
150
|
+
try {
|
|
151
|
+
const decoded = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
152
|
+
if (decoded.version !== 1 ||
|
|
153
|
+
decoded.sessionId !== sessionId ||
|
|
154
|
+
typeof decoded.beforeSegmentId !== 'string' ||
|
|
155
|
+
decoded.beforeSegmentId.length === 0)
|
|
156
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
157
|
+
return decoded;
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw new Error('EVENT_CURSOR_INVALID');
|
|
161
|
+
}
|
|
162
|
+
}
|