@ct-agents/worker 0.2.2 → 0.4.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.
Files changed (2) hide show
  1. package/package.json +7 -7
  2. package/src/index.ts +3 -217
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ct-agents/worker",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -12,12 +12,12 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "zod": "4.4.3",
15
- "@ct-agents/harness": "0.2.2",
16
- "@ct-agents/environment": "0.2.2",
17
- "@ct-agents/prompts": "0.2.2",
18
- "@ct-agents/memory": "0.2.2",
19
- "@ct-agents/protocol": "0.2.2",
20
- "@ct-agents/tools": "0.2.2"
15
+ "@ct-agents/environment": "0.4.0",
16
+ "@ct-agents/harness": "0.4.0",
17
+ "@ct-agents/prompts": "0.4.0",
18
+ "@ct-agents/memory": "0.4.0",
19
+ "@ct-agents/protocol": "0.4.0",
20
+ "@ct-agents/tools": "0.4.0"
21
21
  },
22
22
  "optionalDependencies": {
23
23
  "dockerode": "4.0.9"
package/src/index.ts CHANGED
@@ -1,8 +1,4 @@
1
1
  import type {
2
- ContextResolveQueue,
3
- ContextResolveQueueClaimInput,
4
- ContextResolveResult,
5
- ContextResolveWorkItem,
6
2
  EnvironmentId,
7
3
  EnvironmentExecutionMode,
8
4
  EnvironmentHostedPolicy,
@@ -46,13 +42,6 @@ export { WorkerHttpError } from './worker-http-transport.js';
46
42
  export { DEFAULT_WORK_LEASE_RENEW_INTERVAL_MS } from './leased-item-runner.js';
47
43
 
48
44
  export type ResolveResources = (item: ToolWorkItem) => Promise<ResourceSlots> | ResourceSlots;
49
- export type ResolvePromptContext = (item: ContextResolveWorkItem) =>
50
- Promise<Record<string, unknown>> | Record<string, unknown>;
51
-
52
- export type WorkerContextResolveQueue = Pick<
53
- ContextResolveQueue,
54
- 'claim' | 'renew' | 'complete'
55
- >;
56
45
 
57
46
  export type ResourceOptionsParser = {
58
47
  parse(value: unknown): Record<string, unknown>;
@@ -132,23 +121,6 @@ export type RunEnvironmentWorkerLoopInput = EnvironmentWorkerInput & {
132
121
  onError?: (error: unknown) => void | Promise<void>;
133
122
  };
134
123
 
135
- export type ContextResolverWorkerInput = {
136
- /** 本地/内存队列可选的不透明路由句柄;HTTP client 已由 Environment Key 绑定。 */
137
- scope?: ExecutionScope;
138
- contextResolveQueue: WorkerContextResolveQueue;
139
- resolvePromptContext: ResolvePromptContext;
140
- maxBatchSize?: number;
141
- executionMode?: EnvironmentExecutionMode;
142
- leaseRenewIntervalMs?: number;
143
- onError?: (error: unknown) => void | Promise<void>;
144
- };
145
-
146
- export type RunContextResolverWorkerLoopInput = ContextResolverWorkerInput & {
147
- abortSignal?: AbortSignal;
148
- idleDelayMs?: number;
149
- errorDelayMs?: number;
150
- };
151
-
152
124
  export const DEFAULT_CLAIM_BLOCK_MS = 15_000;
153
125
  const PLATFORM_PROXY_RESOURCE_CAPABILITIES = [
154
126
  { slot: 'skills', implementationId: 'platform-skills-store' },
@@ -486,98 +458,6 @@ export class EnvironmentWorker {
486
458
 
487
459
  }
488
460
 
489
- /**
490
- * 独立 context resolver worker。
491
- *
492
- * 它只处理 context.resolve 工作项,把接入方或平台托管 Resource 物化成 prompt snapshot。
493
- * 该闭环不执行 tool handler,也不会写 tool.called/tool-result 事件。
494
- */
495
- export class ContextResolverWorker {
496
- public constructor(private readonly input: ContextResolverWorkerInput) {}
497
-
498
- public async drainOnce(): Promise<number> {
499
- const executionMode = this.input.executionMode ?? 'self_hosted';
500
- const items = await this.input.contextResolveQueue.claim({
501
- scope: this.input.scope ?? {},
502
- executionMode,
503
- max: this.input.maxBatchSize ?? 1,
504
- });
505
-
506
- await Promise.all(items.map((item) => this.handleItem(item)));
507
- return items.length;
508
- }
509
-
510
- private async handleItem(item: ContextResolveWorkItem): Promise<void> {
511
- const renew = this.input.contextResolveQueue.renew;
512
- const leaseId = item.leaseId;
513
- await runLeasedItem<ContextResolveResult>({
514
- ...(leaseId && renew ? {
515
- renewal: {
516
- intervalMs: this.input.leaseRenewIntervalMs,
517
- renew: () => renew({
518
- workId: item.workId,
519
- leaseId,
520
- scope: item.scope,
521
- executionMode: this.input.executionMode ?? item.executionMode ?? 'self_hosted',
522
- }),
523
- onError: this.input.onError,
524
- },
525
- } : {}),
526
- execute: async () => {
527
- try {
528
- return {
529
- workId: item.workId,
530
- ...(item.leaseId ? { leaseId: item.leaseId } : {}),
531
- status: 'completed',
532
- snapshot: await this.input.resolvePromptContext(item),
533
- };
534
- } catch (error) {
535
- return {
536
- workId: item.workId,
537
- ...(item.leaseId ? { leaseId: item.leaseId } : {}),
538
- status: 'failed',
539
- error: {
540
- message: normalizeContextResolverError(error),
541
- retryable: false,
542
- },
543
- };
544
- }
545
- },
546
- complete: (result) => this.input.contextResolveQueue.complete(
547
- result,
548
- this.buildCompleteScope(item),
549
- ),
550
- completeRejectedError: () => new Error(`context work complete 未被接受:${item.workId}`),
551
- });
552
- }
553
-
554
- private buildCompleteScope(item: ContextResolveWorkItem) {
555
- return {
556
- scope: item.scope,
557
- executionMode: this.input.executionMode ?? item.executionMode ?? 'self_hosted',
558
- leaseId: item.leaseId,
559
- };
560
- }
561
- }
562
-
563
- export async function runContextResolverWorkerLoop(input: RunContextResolverWorkerLoopInput): Promise<void> {
564
- const worker = new ContextResolverWorker(input);
565
- const idleDelayMs = input.idleDelayMs ?? 250;
566
- const errorDelayMs = input.errorDelayMs ?? 1000;
567
-
568
- while (!input.abortSignal?.aborted) {
569
- try {
570
- const drained = await worker.drainOnce();
571
- if (drained === 0) {
572
- await delay(idleDelayMs, input.abortSignal);
573
- }
574
- } catch (error) {
575
- await input.onError?.(error);
576
- await delay(errorDelayMs, input.abortSignal);
577
- }
578
- }
579
- }
580
-
581
461
  export async function runEnvironmentWorkerLoop(input: RunEnvironmentWorkerLoopInput): Promise<void> {
582
462
  const worker = new EnvironmentWorker(input);
583
463
  const idleDelayMs = input.idleDelayMs ?? 250;
@@ -640,8 +520,6 @@ export type HttpWorkQueueClientInput = {
640
520
  signal?: AbortSignal;
641
521
  };
642
522
 
643
- export type HttpContextResolveQueueClientInput = Omit<HttpWorkQueueClientInput, 'workerInstanceId'>;
644
-
645
523
  export function zodObjectToJsonSchema(schema: z.ZodType<unknown>): Record<string, unknown> {
646
524
  const jsonSchema = z.toJSONSchema(schema) as Record<string, unknown>;
647
525
  const normalized = normalizeCatalogJsonSchema(jsonSchema, true);
@@ -738,6 +616,7 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
738
616
  }
739
617
  const data = await this.transport.requestJson('/work/complete', {
740
618
  method: 'POST',
619
+ signal: this.input.signal,
741
620
  body: JSON.stringify({
742
621
  ...result,
743
622
  leaseId: scope.leaseId,
@@ -768,13 +647,13 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
768
647
  };
769
648
  }
770
649
 
771
- public async heartbeat(workerInstanceId: string) {
650
+ public async heartbeat(workerInstanceId: string, signal = this.input.signal) {
772
651
  if (workerInstanceId !== this.input.workerInstanceId) {
773
652
  throw new Error('Worker 心跳身份与 client 实例不一致');
774
653
  }
775
654
  const data = await this.transport.requestJson(`/instances/${encodeURIComponent(workerInstanceId)}/heartbeat`, {
776
655
  method: 'POST',
777
- signal: this.input.signal,
656
+ signal,
778
657
  }, '平台工作队列响应不是 JSON 对象');
779
658
  const record = expectRecord(data, 'Worker 心跳响应缺少 data');
780
659
  return {
@@ -800,54 +679,6 @@ export class HttpWorkQueueClient implements WorkerWorkQueue {
800
679
 
801
680
  }
802
681
 
803
- export class HttpContextResolveQueueClient implements WorkerContextResolveQueue {
804
- private readonly transport: WorkerHttpTransport;
805
-
806
- public constructor(private readonly input: HttpContextResolveQueueClientInput) {
807
- this.transport = new WorkerHttpTransport(input);
808
- }
809
-
810
- public async claim(input: ContextResolveQueueClaimInput) {
811
- const data = await this.transport.requestJson('/context-work/claim', {
812
- method: 'POST',
813
- signal: this.input.signal,
814
- body: JSON.stringify({
815
- max: input.max,
816
- }),
817
- }, '平台 context 工作队列响应不是 JSON 对象');
818
-
819
- const record = expectRecord(data, 'context claim 响应缺少 data');
820
- const items = record.items;
821
- if (!Array.isArray(items)) {
822
- throw new Error('context claim 响应缺少 items 数组');
823
- }
824
- return items.map(parseContextResolveWorkItem);
825
- }
826
-
827
- public async complete(result: ContextResolveResult): Promise<boolean> {
828
- const data = await this.transport.requestJson('/context-work/complete', {
829
- method: 'POST',
830
- body: JSON.stringify(result),
831
- }, '平台 context 工作队列响应不是 JSON 对象');
832
- const record = expectRecord(data, 'context complete 响应缺少 data');
833
- if (record.accepted !== true) {
834
- throw new Error(`context complete 未被接受:${result.workId}`);
835
- }
836
- return true;
837
- }
838
-
839
- public async renew(input: { workId: string; leaseId: string }): Promise<boolean> {
840
- const data = await this.transport.requestJson('/context-work/renew', {
841
- method: 'POST',
842
- signal: this.input.signal,
843
- body: JSON.stringify({ workId: input.workId, leaseId: input.leaseId }),
844
- }, '平台 context 工作队列响应不是 JSON 对象');
845
- const record = expectRecord(data, 'context renew 响应缺少 data');
846
- return record.renewed === true;
847
- }
848
-
849
- }
850
-
851
682
  function toToolExecutionResult(
852
683
  workId: string,
853
684
  execution: ToolExecution<unknown>,
@@ -1160,44 +991,6 @@ function parseExecutionScope(value: unknown): ExecutionScope {
1160
991
  return Object.fromEntries(entries);
1161
992
  }
1162
993
 
1163
- function parseContextResolveWorkItem(value: unknown): ContextResolveWorkItem {
1164
- const record = expectRecord(value, 'context work item 不是对象');
1165
- const workId = expectString(record.workId, 'workId');
1166
- const leaseId = typeof record.leaseId === 'string' && record.leaseId.trim()
1167
- ? record.leaseId.trim()
1168
- : undefined;
1169
- const sessionId = expectString(record.sessionId, 'sessionId');
1170
- const rootSessionId = expectString(record.rootSessionId, 'rootSessionId');
1171
- const request = expectRecord(record.request, 'request 必须是对象');
1172
- const kind = expectString(request.kind, 'request.kind');
1173
- if (kind !== 'prompt_context') {
1174
- throw new Error('request.kind 必须是 prompt_context');
1175
- }
1176
- const rawKeys = request.keys;
1177
- const keys = Array.isArray(rawKeys)
1178
- ? rawKeys
1179
- .filter((key): key is string => typeof key === 'string')
1180
- .map((key) => key.trim())
1181
- .filter(Boolean)
1182
- : [];
1183
- if (!Array.isArray(rawKeys) || keys.length === 0 || keys.length !== rawKeys.length) {
1184
- throw new Error('request.keys 必须是非空字符串数组');
1185
- }
1186
- const executionMode = parseExecutionMode(record.executionMode);
1187
- return {
1188
- workId,
1189
- ...(leaseId ? { leaseId } : {}),
1190
- sessionId,
1191
- rootSessionId,
1192
- scope: parseExecutionScope(record.scope),
1193
- request: {
1194
- kind: 'prompt_context',
1195
- keys,
1196
- },
1197
- ...(executionMode ? { executionMode } : {}),
1198
- };
1199
- }
1200
-
1201
994
  function normalizeWorkerError(error: unknown): string {
1202
995
  if (error instanceof Error) {
1203
996
  return `工具执行失败:${error.message}`;
@@ -1222,13 +1015,6 @@ function toWorkerErrorPayload(error: unknown): NonNullable<ToolExecutionResult['
1222
1015
  };
1223
1016
  }
1224
1017
 
1225
- function normalizeContextResolverError(error: unknown): string {
1226
- if (error instanceof Error) {
1227
- return `context resolve 失败:${error.message}`;
1228
- }
1229
- return `context resolve 失败:${String(error)}`;
1230
- }
1231
-
1232
1018
  function expectString(value: unknown, field: string): string {
1233
1019
  if (typeof value !== 'string' || !value.trim()) {
1234
1020
  throw new Error(`字段 ${field} 必须是非空字符串`);