gaoding-cli 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (41) hide show
  1. package/README.md +2 -2
  2. package/contracts/operations/agent.send/input.schema.json +4 -0
  3. package/contracts/operations/model.get/output.schema.json +1 -0
  4. package/dist/bin/gd-cli.js +37 -16
  5. package/dist/src/bootstrap/create-cli.js +43 -4
  6. package/dist/src/bootstrap/create-runtime.js +44 -13
  7. package/dist/src/cli/action-binding.js +43 -9
  8. package/dist/src/cli/agent-commands.js +13 -3
  9. package/dist/src/cli/auth-commands.js +21 -13
  10. package/dist/src/cli/dam-commands.js +35 -20
  11. package/dist/src/cli/errors.js +34 -1
  12. package/dist/src/cli/model-commands.js +25 -11
  13. package/dist/src/cli/org-commands.js +18 -12
  14. package/dist/src/cli/presenter.js +11 -1
  15. package/dist/src/cli/tool-commands.js +19 -7
  16. package/dist/src/cli/update-command.js +6 -4
  17. package/dist/src/features/agent/creative-agent-adapter.js +19 -14
  18. package/dist/src/features/agent/creative-protocol.js +7 -3
  19. package/dist/src/features/agent/use-cases.js +63 -54
  20. package/dist/src/features/auth/use-cases.js +82 -65
  21. package/dist/src/features/dam/asset-projection.js +22 -14
  22. package/dist/src/features/dam/dam-api-adapter.js +19 -4
  23. package/dist/src/features/dam/object-storage.js +2 -0
  24. package/dist/src/features/dam/registered-uploader.js +58 -35
  25. package/dist/src/features/dam/use-cases.js +39 -32
  26. package/dist/src/features/org/use-cases.js +27 -11
  27. package/dist/src/features/tool/catalog.js +7 -3
  28. package/dist/src/features/tool/dynamic-schema.js +19 -9
  29. package/dist/src/features/tool/mns-catalog-adapter.js +7 -4
  30. package/dist/src/features/tool/tool-api-adapter.js +186 -73
  31. package/dist/src/features/tool/use-cases.js +63 -34
  32. package/dist/src/features/update/update-service.js +28 -22
  33. package/dist/src/platform/json-input.js +3 -0
  34. package/dist/src/platform/remote-error-evidence.js +66 -0
  35. package/dist/src/platform/remote-protocol.js +10 -0
  36. package/dist/src/platform/signed-http-transport.js +31 -5
  37. package/dist/src/telemetry/invocation.js +156 -0
  38. package/dist/src/telemetry/sls-sink.js +31 -0
  39. package/package.json +1 -1
  40. package/skills/gd-cli/references/creation.md +15 -4
  41. package/skills/gd-cli/references/errors.md +3 -1
@@ -11,6 +11,8 @@ function parameterSchema(parameter) {
11
11
  if (parameter.type === "uri[]") {
12
12
  return { type: "array", items: { type: "string", format: "uri" }, ...common };
13
13
  }
14
+ if (parameter.type === "number")
15
+ return { type: "number", ...common };
14
16
  return {
15
17
  type: "string",
16
18
  ...(parameter.options === undefined
@@ -67,29 +69,37 @@ export function buildToolArgumentsSchema(catalog, tool) {
67
69
  additionalProperties: false
68
70
  };
69
71
  }
70
- const ajv = new Ajv2020({ allErrors: true, strict: true });
71
- const addFormats = addFormatsImport;
72
- addFormats(ajv);
73
- function validator(model) {
72
+ export function buildModelArgumentsSchema(model) {
74
73
  const properties = {
75
- model: { type: "string" }
74
+ model: {
75
+ type: "string",
76
+ const: model.model,
77
+ description: "要调用的模型机器标识。"
78
+ }
76
79
  };
77
80
  for (const parameter of model.parameters) {
78
81
  properties[parameter.name] = parameterSchema(parameter);
79
82
  }
80
- return ajv.compile({
83
+ return {
81
84
  type: "object",
82
85
  properties,
83
86
  required: [
84
87
  "model",
85
- ...model.parameters.filter((parameter) => parameter.required).map((parameter) => parameter.name)
88
+ ...model.parameters
89
+ .filter((parameter) => parameter.required)
90
+ .map((parameter) => parameter.name)
86
91
  ],
87
92
  additionalProperties: false
88
- });
93
+ };
94
+ }
95
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
96
+ const addFormats = addFormatsImport;
97
+ addFormats(ajv);
98
+ function validator(model) {
99
+ return ajv.compile(buildModelArgumentsSchema(model));
89
100
  }
90
101
  export function assertModelArguments(model, value) {
91
102
  if (typeof value !== "object" || value === null
92
- || value.model !== model.model
93
103
  || !validator(model)(value)) {
94
104
  throw new ToolInputError();
95
105
  }
@@ -18,6 +18,7 @@ const toolOrder = {
18
18
  };
19
19
  const multiUriKeys = new Set([
20
20
  "image_urls",
21
+ "reference_images",
21
22
  "file_urls",
22
23
  "video_urls",
23
24
  "audio_urls"
@@ -98,9 +99,11 @@ function parameter(value) {
98
99
  ? "uri[]"
99
100
  : name === "start_frame" || name === "end_frame" || sourceType === "IMAGE"
100
101
  ? "uri"
101
- : sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
102
- ? "string"
103
- : undefined;
102
+ : sourceType === "NUMBER"
103
+ ? "number"
104
+ : sourceType === "TEXT" || sourceType === "INPUT" || sourceType === "SELECT"
105
+ ? "string"
106
+ : undefined;
104
107
  if (type === undefined)
105
108
  throw new Error("unsupported parameter");
106
109
  let options;
@@ -120,7 +123,7 @@ function parameter(value) {
120
123
  if (options.length === 0)
121
124
  throw new Error("empty options");
122
125
  }
123
- const description = text(field.label) ?? text(field.description);
126
+ const description = text(field.description) ?? text(field.label);
124
127
  const defaultValue = field.default_value;
125
128
  const hasDefault = defaultValue !== undefined
126
129
  && defaultValue !== null
@@ -1,6 +1,8 @@
1
1
  import { randomInt } from "node:crypto";
2
2
  import { setTimeout as delay } from "node:timers/promises";
3
3
  import { RemoteRequestError } from "../../platform/signed-http-transport.js";
4
+ import { remoteErrorEvidence } from "../../platform/remote-error-evidence.js";
5
+ import { RemoteProtocolError } from "../../platform/remote-protocol.js";
4
6
  const MAX_JAVA_LONG = 9223372036854775807n;
5
7
  function isRecord(value) {
6
8
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -8,6 +10,9 @@ function isRecord(value) {
8
10
  function fail() {
9
11
  throw new RemoteRequestError();
10
12
  }
13
+ function protocolFailure(value) {
14
+ throw new RemoteProtocolError(remoteErrorEvidence(value));
15
+ }
11
16
  function nonBlank(value) {
12
17
  if (typeof value !== "string" || value.trim() === "")
13
18
  return fail();
@@ -43,9 +48,17 @@ function parseIntent(value) {
43
48
  };
44
49
  }
45
50
  function submittedTask(value) {
46
- if (!isRecord(value) || !isRecord(value.result) || value.result.isError !== false)
47
- return fail();
48
- return nonBlank(value.task_id);
51
+ if (!isRecord(value) || !isRecord(value.result))
52
+ return protocolFailure(value);
53
+ if (value.result.isError === true) {
54
+ throw new ToolTaskFailedError(remoteErrorEvidence(value));
55
+ }
56
+ if (value.result.isError !== false)
57
+ return protocolFailure(value);
58
+ if (typeof value.task_id !== "string" || value.task_id.trim() === "") {
59
+ return protocolFailure(value);
60
+ }
61
+ return value.task_id;
49
62
  }
50
63
  function resourceName(uri) {
51
64
  let url;
@@ -53,32 +66,42 @@ function resourceName(uri) {
53
66
  url = new URL(uri);
54
67
  }
55
68
  catch {
56
- return fail();
69
+ return protocolFailure(uri);
57
70
  }
58
71
  const segment = url.pathname.split("/").filter(Boolean).at(-1);
59
72
  if (segment === undefined)
60
- return fail();
73
+ return protocolFailure(uri);
61
74
  try {
62
- return nonBlank(decodeURIComponent(segment));
75
+ const decoded = decodeURIComponent(segment);
76
+ if (decoded.trim() === "")
77
+ return protocolFailure(uri);
78
+ return decoded;
63
79
  }
64
80
  catch {
65
- return fail();
81
+ return protocolFailure(uri);
66
82
  }
67
83
  }
68
84
  function normalizeContent(value) {
69
85
  if (!isRecord(value))
70
- return fail();
86
+ return protocolFailure(value);
71
87
  if (value.type === "text") {
72
- return { type: "text", text: nonBlank(value.text) };
88
+ if (typeof value.text !== "string" || value.text.trim() === "") {
89
+ return protocolFailure(value);
90
+ }
91
+ return { type: "text", text: value.text };
92
+ }
93
+ if (value.type !== "resource" || !isRecord(value.resource)) {
94
+ return protocolFailure(value);
73
95
  }
74
- if (value.type !== "resource" || !isRecord(value.resource))
75
- return fail();
76
96
  if ("blob" in value.resource || "text" in value.resource)
77
- return fail();
78
- const uri = nonBlank(value.resource.uri);
97
+ return protocolFailure(value);
98
+ if (typeof value.resource.uri !== "string" || value.resource.uri.trim() === "") {
99
+ return protocolFailure(value);
100
+ }
101
+ const uri = value.resource.uri;
79
102
  const mimeType = value.resource.mimeType;
80
103
  if (mimeType !== undefined && (typeof mimeType !== "string" || mimeType.trim() === "")) {
81
- return fail();
104
+ return protocolFailure(value);
82
105
  }
83
106
  return {
84
107
  type: "resource_link",
@@ -87,23 +110,102 @@ function normalizeContent(value) {
87
110
  ...(mimeType === undefined ? {} : { mimeType })
88
111
  };
89
112
  }
113
+ export class ToolTaskFailedError extends Error {
114
+ remoteCode;
115
+ remoteBody;
116
+ constructor(evidence = {}) {
117
+ super("稿定工具任务执行失败。");
118
+ this.name = "ToolTaskFailedError";
119
+ this.remoteCode = evidence.remoteCode;
120
+ this.remoteBody = evidence.remoteBody;
121
+ }
122
+ }
123
+ function failedPoll(error, difyTaskId) {
124
+ return {
125
+ state: "failed",
126
+ error,
127
+ ...(difyTaskId === undefined ? {} : { difyTaskId })
128
+ };
129
+ }
130
+ function optionalInnerDifyTaskId(value) {
131
+ if (typeof value !== "string" || value.trim() === "")
132
+ return undefined;
133
+ try {
134
+ const parsed = JSON.parse(value);
135
+ if (!isRecord(parsed))
136
+ return undefined;
137
+ const difyTaskId = parsed.dify_task_id;
138
+ return typeof difyTaskId === "string" && difyTaskId.trim() !== ""
139
+ ? difyTaskId
140
+ : undefined;
141
+ }
142
+ catch {
143
+ return undefined;
144
+ }
145
+ }
90
146
  function pollResult(value, taskId) {
91
147
  if (!Array.isArray(value))
92
- return fail();
148
+ return protocolFailure(value);
93
149
  const item = value.find((candidate) => isRecord(candidate) && candidate.task_id === taskId);
94
- if (!isRecord(item) || item.code !== "200")
95
- return fail();
96
- const inner = parseRecord(nonBlank(item.result));
97
- if (!isRecord(inner.result) || inner.result.isError !== false)
98
- return fail();
150
+ if (!isRecord(item))
151
+ return { state: "pending" };
152
+ if (typeof item.code !== "string" || item.code.trim() === "") {
153
+ return protocolFailure(item);
154
+ }
155
+ if (item.code !== "200") {
156
+ return failedPoll(new ToolTaskFailedError(remoteErrorEvidence(item)), optionalInnerDifyTaskId(item.result));
157
+ }
158
+ if (typeof item.result !== "string" || item.result.trim() === "") {
159
+ return protocolFailure(item);
160
+ }
161
+ let inner;
162
+ try {
163
+ const parsed = JSON.parse(item.result);
164
+ if (!isRecord(parsed))
165
+ return protocolFailure(item);
166
+ inner = parsed;
167
+ }
168
+ catch (error) {
169
+ if (error instanceof RemoteProtocolError)
170
+ throw error;
171
+ return protocolFailure(item);
172
+ }
173
+ const difyTaskId = inner.dify_task_id;
174
+ if (difyTaskId !== undefined
175
+ && (typeof difyTaskId !== "string" || difyTaskId.trim() === "")) {
176
+ return protocolFailure(inner);
177
+ }
178
+ if (!isRecord(inner.result)) {
179
+ return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
180
+ }
181
+ if (inner.result.isError === true) {
182
+ return failedPoll(new ToolTaskFailedError(remoteErrorEvidence(inner)), difyTaskId);
183
+ }
184
+ if (inner.result.isError !== false) {
185
+ return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
186
+ }
99
187
  const content = inner.result.content;
100
- if (content === undefined)
101
- return undefined;
102
- if (!Array.isArray(content))
103
- return fail();
104
- if (content.length === 0)
105
- return undefined;
106
- return content.map(normalizeContent);
188
+ if (content === undefined || (Array.isArray(content) && content.length === 0)) {
189
+ return {
190
+ state: "pending",
191
+ ...(difyTaskId === undefined ? {} : { difyTaskId })
192
+ };
193
+ }
194
+ if (!Array.isArray(content)) {
195
+ return failedPoll(new RemoteProtocolError(remoteErrorEvidence(inner)), difyTaskId);
196
+ }
197
+ try {
198
+ return {
199
+ state: "complete",
200
+ content: content.map(normalizeContent),
201
+ ...(difyTaskId === undefined ? {} : { difyTaskId })
202
+ };
203
+ }
204
+ catch (error) {
205
+ if (error instanceof RemoteProtocolError)
206
+ return failedPoll(error, difyTaskId);
207
+ throw error;
208
+ }
107
209
  }
108
210
  async function safePost(transport, request) {
109
211
  try {
@@ -112,7 +214,7 @@ async function safePost(transport, request) {
112
214
  catch (error) {
113
215
  if (request.signal.aborted)
114
216
  throw request.signal.reason;
115
- if (error instanceof RemoteRequestError)
217
+ if (error instanceof RemoteRequestError || error instanceof RemoteProtocolError)
116
218
  throw error;
117
219
  throw new RemoteRequestError();
118
220
  }
@@ -138,7 +240,7 @@ export function createToolApiAdapter(dependencies) {
138
240
  async execute({ invocation, access, signal }) {
139
241
  signal.throwIfAborted();
140
242
  const managedContentId = contentId(createContentId());
141
- const intent = parseIntent(await safePost(dependencies.transport, {
243
+ const intent = await dependencies.telemetry.stage("intent", async () => parseIntent(await safePost(dependencies.transport, {
142
244
  path: "/ai-agent/v1/tool-intent",
143
245
  body: {
144
246
  prompt: invocation.prompt,
@@ -148,53 +250,64 @@ export function createToolApiAdapter(dependencies) {
148
250
  credential: access.credential,
149
251
  organizationId: access.organizationId,
150
252
  signal
151
- }));
253
+ })));
152
254
  signal.throwIfAborted();
153
- const taskId = submittedTask(await safePost(dependencies.transport, {
154
- path: "/gdesign/tool/v1/dify/call_async",
155
- body: {
156
- jsonrpc: "2.0",
157
- id: 0,
158
- method: "tools/call",
159
- params: {
160
- name: intent.name,
161
- arguments: { ...intent.arguments, content_id: managedContentId }
255
+ const taskId = await dependencies.telemetry.stage("submit", async () => {
256
+ dependencies.telemetry.annotate({ content_id: managedContentId });
257
+ return submittedTask(await safePost(dependencies.transport, {
258
+ path: "/gdesign/tool/v1/dify/call_async",
259
+ body: {
260
+ jsonrpc: "2.0",
261
+ id: 0,
262
+ method: "tools/call",
263
+ params: {
264
+ name: intent.name,
265
+ arguments: { ...intent.arguments, content_id: managedContentId }
266
+ }
267
+ },
268
+ credential: access.credential,
269
+ organizationId: access.organizationId,
270
+ signal
271
+ }));
272
+ });
273
+ dependencies.telemetry.annotate({ task_id: taskId });
274
+ return dependencies.telemetry.stage("wait", async () => {
275
+ let transientFailures = 0;
276
+ while (true) {
277
+ signal.throwIfAborted();
278
+ let response;
279
+ try {
280
+ response = await safePost(dependencies.transport, {
281
+ path: "/gdesign/tool/v1/dify/process/batch",
282
+ body: { task_ids: [taskId] },
283
+ credential: access.credential,
284
+ organizationId: access.organizationId,
285
+ signal
286
+ });
287
+ transientFailures = 0;
162
288
  }
163
- },
164
- credential: access.credential,
165
- organizationId: access.organizationId,
166
- signal
167
- }));
168
- let transientFailures = 0;
169
- while (true) {
170
- signal.throwIfAborted();
171
- let response;
172
- try {
173
- response = await safePost(dependencies.transport, {
174
- path: "/gdesign/tool/v1/dify/process/batch",
175
- body: { task_ids: [taskId] },
176
- credential: access.credential,
177
- organizationId: access.organizationId,
178
- signal
179
- });
180
- transientFailures = 0;
181
- }
182
- catch (error) {
183
- if (signal.aborted)
184
- throw signal.reason;
185
- if (!(error instanceof RemoteRequestError) || !error.transient)
186
- throw error;
187
- transientFailures += 1;
188
- if (transientFailures > 3)
189
- throw error;
190
- await sleep(transientFailures * 1_000, signal);
191
- continue;
289
+ catch (error) {
290
+ if (signal.aborted)
291
+ throw signal.reason;
292
+ if (!(error instanceof RemoteRequestError) || !error.transient)
293
+ throw error;
294
+ transientFailures += 1;
295
+ if (transientFailures > 3)
296
+ throw error;
297
+ await sleep(transientFailures * 1_000, signal);
298
+ continue;
299
+ }
300
+ const poll = pollResult(response, taskId);
301
+ if (poll.difyTaskId !== undefined) {
302
+ dependencies.telemetry.annotate({ dify_task_id: poll.difyTaskId });
303
+ }
304
+ if (poll.state === "failed")
305
+ throw poll.error;
306
+ if (poll.state === "complete")
307
+ return poll.content;
308
+ await sleep(2_000, signal);
192
309
  }
193
- const result = pollResult(response, taskId);
194
- if (result !== undefined)
195
- return result;
196
- await sleep(2_000, signal);
197
- }
310
+ });
198
311
  }
199
312
  };
200
313
  }
@@ -48,7 +48,7 @@ function remoteMedia(value, name) {
48
48
  export function createToolUseCases(dependencies) {
49
49
  async function load(access, signal) {
50
50
  signal.throwIfAborted();
51
- const catalog = await dependencies.catalog.load({ access, signal });
51
+ const catalog = await dependencies.telemetry.stage("catalog", () => (dependencies.catalog.load({ access, signal })));
52
52
  signal.throwIfAborted();
53
53
  for (const warning of catalog.warnings)
54
54
  dependencies.warn(warning);
@@ -69,48 +69,77 @@ export function createToolUseCases(dependencies) {
69
69
  },
70
70
  async call({ input, access, signal }) {
71
71
  const catalog = await load(access, signal);
72
- const modelName = input.arguments.model;
73
- if (typeof modelName !== "string")
74
- throw new ToolInputError();
75
- const model = findToolModel(catalog, input.name, modelName);
76
- assertModelArguments(model, input.arguments);
72
+ const model = await dependencies.telemetry.stage("input", () => {
73
+ const modelName = input.arguments.model;
74
+ if (typeof modelName !== "string")
75
+ throw new ToolInputError();
76
+ const selected = findToolModel(catalog, input.name, modelName);
77
+ assertModelArguments(selected, input.arguments);
78
+ return selected;
79
+ });
80
+ const { model: ignoredModel, ...parameters } = input.arguments;
81
+ void ignoredModel;
82
+ dependencies.telemetry.annotate({
83
+ tool_name: model.tool,
84
+ model_id: model.model,
85
+ parameters
86
+ });
77
87
  const prompt = [];
78
- const parameters = {};
79
- for (const parameter of model.parameters) {
80
- signal.throwIfAborted();
81
- const value = input.arguments[parameter.name];
82
- if (value === undefined)
83
- continue;
84
- if (parameter.type === "string") {
85
- if (parameter.name === "prompt") {
86
- prompt.push({ type: "text", content: value });
88
+ const requestParameters = {};
89
+ const prepare = async () => {
90
+ for (const parameter of model.parameters) {
91
+ signal.throwIfAborted();
92
+ const value = input.arguments[parameter.name];
93
+ if (value === undefined)
94
+ continue;
95
+ if (parameter.type === "string") {
96
+ if (parameter.name === "prompt") {
97
+ prompt.push({ type: "text", content: value });
98
+ }
99
+ else {
100
+ requestParameters[parameter.name] = value;
101
+ }
102
+ continue;
87
103
  }
88
- else {
89
- parameters[parameter.name] = value;
104
+ if (parameter.type === "number") {
105
+ requestParameters[parameter.name] = value;
106
+ continue;
90
107
  }
91
- continue;
92
- }
93
- const values = parameter.type === "uri[]" ? value : [value];
94
- for (const mediaUrl of values) {
95
- signal.throwIfAborted();
96
- if (mediaUrl.startsWith("file:")) {
97
- const uploaded = await dependencies.uploader.upload({ url: mediaUrl }, access, signal);
108
+ const values = parameter.type === "uri[]" ? value : [value];
109
+ for (const mediaUrl of values) {
98
110
  signal.throwIfAborted();
99
- prompt.push({
100
- type: "media",
101
- url: uploaded.url,
102
- mime: uploaded.mediaType,
103
- name: parameter.name
104
- });
105
- }
106
- else {
107
- prompt.push(remoteMedia(mediaUrl, parameter.name));
111
+ if (mediaUrl.startsWith("file:")) {
112
+ const uploaded = await dependencies.uploader.upload({ url: mediaUrl }, access, signal);
113
+ signal.throwIfAborted();
114
+ prompt.push({
115
+ type: "media",
116
+ url: uploaded.url,
117
+ mime: uploaded.mediaType,
118
+ name: parameter.name
119
+ });
120
+ }
121
+ else {
122
+ prompt.push(remoteMedia(mediaUrl, parameter.name));
123
+ }
108
124
  }
109
125
  }
126
+ };
127
+ const hasLocalMedia = model.parameters.some((parameter) => {
128
+ const value = input.arguments[parameter.name];
129
+ return parameter.type === "uri"
130
+ ? typeof value === "string" && value.startsWith("file:")
131
+ : parameter.type === "uri[]"
132
+ && Array.isArray(value) && value.some((url) => url.startsWith("file:"));
133
+ });
134
+ if (hasLocalMedia) {
135
+ await dependencies.telemetry.stage("upload", prepare);
136
+ }
137
+ else {
138
+ await prepare();
110
139
  }
111
140
  signal.throwIfAborted();
112
141
  const content = await dependencies.execution.execute({
113
- invocation: { model: model.model, prompt, parameters },
142
+ invocation: { model: model.model, prompt, parameters: requestParameters },
114
143
  access,
115
144
  signal
116
145
  });
@@ -57,20 +57,22 @@ export function createUpdateService(options) {
57
57
  return {
58
58
  async run(signal) {
59
59
  signal.throwIfAborted();
60
- const latest = await fetchLatest(fetchPackage, signal);
60
+ const latest = await options.telemetry.stage("registry", () => (fetchLatest(fetchPackage, signal)));
61
61
  signal.throwIfAborted();
62
62
  const skillOptions = {
63
63
  env,
64
64
  ...(options.homeDirectory ? { homeDirectory: options.homeDirectory } : {})
65
65
  };
66
66
  if (!gt(latest, options.currentVersion)) {
67
- try {
68
- await syncSkills(options.bundledSkills, skillOptions);
69
- }
70
- catch (error) {
71
- rethrowAbort(signal);
72
- throw new UpdateError("UPDATE_FAILED", `gd-cli ${options.currentVersion} 无需更新,但 Agent Skill 同步失败。`, ["请解决冲突后重新执行 gd-cli update。"]);
73
- }
67
+ await options.telemetry.stage("sync", async () => {
68
+ try {
69
+ await syncSkills(options.bundledSkills, skillOptions);
70
+ }
71
+ catch (error) {
72
+ rethrowAbort(signal);
73
+ throw new UpdateError("UPDATE_FAILED", `gd-cli ${options.currentVersion} 无需更新,但 Agent Skill 同步失败。`, ["请解决冲突后重新执行 gd-cli update。"]);
74
+ }
75
+ });
74
76
  return { version: options.currentVersion, updated: false };
75
77
  }
76
78
  const binaryPath = options.binaryPath ?? process.argv[1] ?? "";
@@ -89,25 +91,29 @@ export function createUpdateService(options) {
89
91
  const args = source.manager === "npm"
90
92
  ? ["install", "--global", "--allow-scripts=gaoding-cli", `gaoding-cli@${latest}`]
91
93
  : ["add", "--global", "--allow-build=gaoding-cli", `gaoding-cli@${latest}`];
92
- try {
93
- await runProcess(command, args, signal);
94
- }
95
- catch (error) {
96
- rethrowAbort(signal);
97
- throw new UpdateError("UPDATE_FAILED", `gd-cli 更新到 ${latest} 失败。`, nextSteps);
98
- }
94
+ await options.telemetry.stage("install", async () => {
95
+ try {
96
+ await runProcess(command, args, signal);
97
+ }
98
+ catch (error) {
99
+ rethrowAbort(signal);
100
+ throw new UpdateError("UPDATE_FAILED", `gd-cli 更新到 ${latest} 失败。`, nextSteps);
101
+ }
102
+ });
99
103
  signal.throwIfAborted();
100
104
  const targetSkills = options.bundledSkills.map((skill) => ({
101
105
  ...skill,
102
106
  version: latest
103
107
  }));
104
- try {
105
- await verifySkills(targetSkills, skillOptions);
106
- }
107
- catch (error) {
108
- rethrowAbort(signal);
109
- throw new UpdateError("UPDATE_PARTIAL", `gd-cli 已更新到 ${latest},但 Agent Skill 验收失败。`, ["请重新执行 gd-cli update。"]);
110
- }
108
+ await options.telemetry.stage("verify", async () => {
109
+ try {
110
+ await verifySkills(targetSkills, skillOptions);
111
+ }
112
+ catch (error) {
113
+ rethrowAbort(signal);
114
+ throw new UpdateError("UPDATE_PARTIAL", `gd-cli 已更新到 ${latest},但 Agent Skill 验收失败。`, ["请重新执行 gd-cli update。"]);
115
+ }
116
+ });
111
117
  return { version: latest, updated: true };
112
118
  }
113
119
  };
@@ -52,6 +52,9 @@ function readStream(stream, signal) {
52
52
  };
53
53
  const onAbort = () => {
54
54
  cleanup();
55
+ const destroy = stream.destroy;
56
+ if (typeof destroy === "function")
57
+ destroy.call(stream);
55
58
  reject(signal.reason);
56
59
  };
57
60
  stream.on("data", onData);