@huaqiu/dsh-tool-schematic-gen 0.3.7 → 0.3.9

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/lib/index.mjs CHANGED
@@ -787,6 +787,33 @@ async function createPreviewArtifact(env, type, filename, content, contentEncodi
787
787
  });
788
788
  }
789
789
  /**
790
+ * Best-effort cross-process URI for a stored artifact (`file://` to its
791
+ * content on disk). This is what the Place action hands to HQ Edge — an
792
+ * artifact *id* is store-local and meaningless there. Never throws: a
793
+ * generation must not fail because the URI is unavailable; the card simply
794
+ * hides Place when there is no uri.
795
+ */
796
+ async function artifactUriOf(env, id) {
797
+ try {
798
+ if (!env.artifacts || typeof env.artifacts.getDownloadUri !== "function") return null;
799
+ return await env.artifacts.getDownloadUri(id);
800
+ } catch (err) {
801
+ console.warn(LOG_TAG$1, "getDownloadUri failed for", id, String(err?.message || err));
802
+ return null;
803
+ }
804
+ }
805
+ async function toArtifactEntry(env, created) {
806
+ const entry = {
807
+ id: created.id,
808
+ type: created.type,
809
+ filename: created.filename,
810
+ size: created.size
811
+ };
812
+ const uri = await artifactUriOf(env, created.id);
813
+ if (uri) entry.uri = uri;
814
+ return entry;
815
+ }
816
+ /**
790
817
  * Pull the schematic deliverable out of the final agent state. `schFiles`
791
818
  * carry the `.kicad_sch` content inline, so the text is returned verbatim.
792
819
  */
@@ -826,12 +853,7 @@ async function materializeSchematicArtifacts(env, schFiles) {
826
853
  const file = schFiles[i];
827
854
  try {
828
855
  const created = await createPreviewArtifact(env, "schematic", file.filename, file.content);
829
- artifacts.push({
830
- id: created.id,
831
- type: created.type,
832
- filename: created.filename,
833
- size: created.size
834
- });
856
+ artifacts.push(await toArtifactEntry(env, created));
835
857
  } catch (storeErr) {
836
858
  anyFailed = true;
837
859
  outFiles[i].content = file.content;
@@ -979,13 +1001,7 @@ async function runGenerateSystem(args, exec, env) {
979
1001
  const notes = [];
980
1002
  let zipArtifact = null;
981
1003
  try {
982
- const created = await createPreviewArtifact(env, "zip", sanitizeZipBaseName(designName) + ".zip", zipBuf.toString("base64"), "base64");
983
- zipArtifact = {
984
- id: created.id,
985
- type: created.type,
986
- filename: created.filename,
987
- size: created.size
988
- };
1004
+ zipArtifact = await toArtifactEntry(env, await createPreviewArtifact(env, "zip", sanitizeZipBaseName(designName) + ".zip", zipBuf.toString("base64"), "base64"));
989
1005
  } catch (storeErr) {
990
1006
  notes.push("Could not store the project zip as an artifact (" + String(storeErr?.message || storeErr) + "); kept it in the result instead.");
991
1007
  }
@@ -1015,7 +1031,7 @@ const AUTH_GATE_NOTE = "AUTH: This tool requires a Huaqiu EDA (eda.cn) account.
1015
1031
  function createSchematicTool(env) {
1016
1032
  return defineTool({
1017
1033
  name: "generate_schematic_from_description",
1018
- description: "Generate a KiCad schematic (.kicad_sch files) from a natural-language description of a circuit or sub-circuit — e.g. \"design a 5V LM7805 linear regulator power supply with input and output filter capacitors\". Calls the online HQ-EDA schematic generation agent and returns schFiles (filename references), schArtifacts (preview artifact references with id/type/filename/size per sheet), kicadPro and project_achieve_url. Use this when the user asks to draw, generate or create a circuit schematic from a description (not from an image — for that use the symbol/footprint tools). IMPORTANT: The generated schematic renders automatically as a result card in the web client — an interactive canvas preview per sheet (multi-sheet results get a sheet tab bar) and a download button for the current sheet. Do NOT paste the schematic source, file URLs, or any fenced code block into your reply; just note in one line that the schematic was generated and how many sheets it has. " + AUTH_GATE_NOTE,
1034
+ description: "Generate a KiCad schematic (.kicad_sch files) from a natural-language description of a circuit or sub-circuit — e.g. \"design a 5V LM7805 linear regulator power supply with input and output filter capacitors\". Calls the online HQ-EDA schematic generation agent and returns schFiles (filename references), schArtifacts (preview artifact references with id/type/filename/size plus a uri when the cross-process placement channel is available), kicadPro and project_achieve_url. Use this when the user asks to draw, generate or create a circuit schematic from a description (not from an image — for that use the symbol/footprint tools). IMPORTANT: The generated schematic renders automatically as a result card in the web client — an interactive canvas preview per sheet (multi-sheet results get a sheet tab bar) and a download button for the current sheet. Do NOT paste the schematic source, file URLs, or any fenced code block into your reply; just note in one line that the schematic was generated and how many sheets it has. " + AUTH_GATE_NOTE,
1019
1035
  parameters: {
1020
1036
  description: {
1021
1037
  type: "string",
@@ -1040,7 +1056,7 @@ function createSchematicTool(env) {
1040
1056
  function createSystemTool(env) {
1041
1057
  return defineTool({
1042
1058
  name: "generate_system_module_graph",
1043
- description: "Generate a hardware system design (module graph) from a natural-language description — e.g. \"design a small smart alarm clock\". Calls the online HQ-EDA system-design agent, which plans the modules, searches/selects parts, wires the connections, and produces a module graph; the graph is then exported to a KiCad project zip. Returns: a zipArtifact reference (preview-artifact id of the full project zip — the zip is never inlined into the conversation) and a summary (design name, module count, connection count, module names). Use this when the user wants a whole system/module-level design, not a single schematic or symbol. IMPORTANT: The generated system design renders automatically as a result card in the web client — a canvas preview of the project root schematic (fetched from the zip artifact) and a Download button for the full project zip. Do NOT paste the schematic source, file URLs, or any fenced code block into your reply; just note in one line that the design was generated, its module count, and that the project zip is downloadable from the card. " + AUTH_GATE_NOTE,
1059
+ description: "Generate a hardware system design (module graph) from a natural-language description — e.g. \"design a small smart alarm clock\". Calls the online HQ-EDA system-design agent, which plans the modules, searches/selects parts, wires the connections, and produces a module graph; the graph is then exported to a KiCad project zip. Returns: a zipArtifact reference (preview-artifact id of the full project zip — the zip is never inlined into the conversation; a uri field is included when the cross-process placement channel is available) and a summary (design name, module count, connection count, module names). Use this when the user wants a whole system/module-level design, not a single schematic or symbol. IMPORTANT: The generated system design renders automatically as a result card in the web client — a canvas preview of the project root schematic (fetched from the zip artifact) and a Download button for the full project zip. Do NOT paste the schematic source, file URLs, or any fenced code block into your reply; just note in one line that the design was generated, its module count, and that the project zip is downloadable from the card. " + AUTH_GATE_NOTE,
1044
1060
  parameters: {
1045
1061
  description: {
1046
1062
  type: "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@huaqiu/dsh-tool-schematic-gen",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "type": "module",
5
5
  "main": "./lib/index.mjs",
6
6
  "types": "./lib/index.d.mts",
@@ -30,8 +30,8 @@
30
30
  "@deepseek-ai/cordis": "^4.0.1",
31
31
  "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.0",
32
32
  "@deepseek-ai/dsh-tools": "^0.1.0-rc.0",
33
- "@huaqiu/dsh-auth": "^0.3.7",
34
- "@huaqiu/dsh-artifacts": "^0.3.7",
33
+ "@huaqiu/dsh-auth": "^0.3.9",
34
+ "@huaqiu/dsh-artifacts": "^0.3.9",
35
35
  "react": "^18"
36
36
  },
37
37
  "devDependencies": {
@@ -39,9 +39,9 @@
39
39
  "@huaqiu/kicad-sexpr-parser": "^0.1.1",
40
40
  "@types/react": "^18.3.31",
41
41
  "react": "^18.3.1",
42
- "@huaqiu/dsh-tool-uncollapse": "0.3.7",
43
- "@huaqiu/dsh-auth": "0.3.7",
44
- "@huaqiu/dsh-artifacts": "0.3.7"
42
+ "@huaqiu/dsh-artifacts": "0.3.9",
43
+ "@huaqiu/dsh-auth": "0.3.9",
44
+ "@huaqiu/dsh-tool-uncollapse": "0.3.9"
45
45
  },
46
46
  "files": [
47
47
  "lib",
@@ -22,6 +22,7 @@ import {
22
22
  resolveArtifactText, resolveArtifactBytes, renderSheetToCanvas,
23
23
  renderProjectZipToCanvas, sizeCanvasFor, downloadText, downloadBytes,
24
24
  } from './ecad.js'
25
+ import { placeSupportOf, type HqEdgePlaceLike } from './place.js'
25
26
  import { useLocale, useTheme } from './theme.js'
26
27
  import { buildLoginUrl, loginIframeBackground } from './login-url.js'
27
28
  import { LiveProgress } from './stack-frame.jsx'
@@ -52,6 +53,13 @@ export interface GenHitProps {
52
53
  inspect?: () => void
53
54
  authState?: AuthStateLike
54
55
  sendPrompt?: PromptSender
56
+ /**
57
+ * Lazy accessor for the host's `hqEdge` service (edge-bridge browser half).
58
+ * Absent/undefined in standalone DSH — the Place button is then hidden.
59
+ * Lazy (not captured once) so plugin load order and HQ Edge restarts both
60
+ * resolve correctly at click time.
61
+ */
62
+ getHqEdge?: () => HqEdgePlaceLike | undefined
55
63
  }
56
64
 
57
65
  function kindOf(toolName: string): 'schematic' | 'system' {
@@ -267,6 +275,9 @@ export const GenHit = memo(function GenHit(props: GenHitProps): ReactElement {
267
275
  }, [artifactKey, state.phase])
268
276
 
269
277
  const [busy, setBusy] = useState<string | null>(null)
278
+ // Outcome of the last Place attempt: 'ok' | 'error:<detail>'. Rendered as a
279
+ // one-line status under the actions so the user knows the placement landed.
280
+ const [placeStatus, setPlaceStatus] = useState<string | null>(null)
270
281
 
271
282
  function onDownload(): void {
272
283
  if (busy || payload.phase !== 'ready') return
@@ -298,6 +309,59 @@ export const GenHit = memo(function GenHit(props: GenHitProps): ReactElement {
298
309
  )
299
310
  }
300
311
 
312
+ /**
313
+ * Place the generated design into the host EDA editor via HQ Edge.
314
+ *
315
+ * A system design sends ONE request (the project zip; HQ Edge derives the
316
+ * root from the `*.kicad_pro` name — the same convention as the preview).
317
+ * A multi-sheet schematic sends one request per sheet so every sheet lands
318
+ * in the editor. Failures are collected and reported as a one-line status;
319
+ * a partial success is explicitly surfaced, not swallowed.
320
+ */
321
+ function placeRequestsOf(): Array<{ artifactUri: string; filename: string | null }> {
322
+ if (!result) return []
323
+ // System kind: exactly one zip artifact (one request; HQ Edge derives the
324
+ // root from the `*.kicad_pro` name inside the zip). Schematic kind: one
325
+ // request per sheet. The wire shape is identical either way.
326
+ return result.artifacts
327
+ .filter((a) => a.uri)
328
+ .map((a) => ({ artifactUri: a.uri!, filename: a.filename }))
329
+ }
330
+
331
+ async function onPlace(): Promise<void> {
332
+ if (busy) return
333
+ const support = placeSupportOf(props.getHqEdge, 'schematic')?.()
334
+ if (!support) return
335
+ const requests = placeRequestsOf()
336
+ if (requests.length === 0) return
337
+ setBusy('place')
338
+ setPlaceStatus(null)
339
+ let placed = 0
340
+ const failures: string[] = []
341
+ for (const req of requests) {
342
+ try {
343
+ await support.place({
344
+ type: 'schematic',
345
+ artifactUri: req.artifactUri,
346
+ ...(req.filename ? { filename: req.filename } : {}),
347
+ })
348
+ placed++
349
+ } catch (err) {
350
+ const detail = String((err as Error)?.message || err)
351
+ console.warn('[hq-schematic-gen] place failed', detail)
352
+ failures.push(detail)
353
+ }
354
+ }
355
+ setBusy(null)
356
+ if (failures.length === 0) {
357
+ setPlaceStatus(t('card.place.done', { count: placed }))
358
+ } else if (placed > 0) {
359
+ setPlaceStatus(t('card.place.partial', { placed, failed: failures.length }) + ' ' + failures[0])
360
+ } else {
361
+ setPlaceStatus(t('card.place.failed') + ' ' + failures[0])
362
+ }
363
+ }
364
+
301
365
  // needs_auth
302
366
  if (state.phase === 'needs_auth') {
303
367
  return <LoginCard toolName={props.toolName} authState={props.authState} t={t} />
@@ -355,6 +419,12 @@ export const GenHit = memo(function GenHit(props: GenHitProps): ReactElement {
355
419
  }
356
420
 
357
421
  const canDownload = payload.phase === 'ready' && (payload.source != null || payload.bytes != null)
422
+ // Place is offered only when the host provides hqEdge, the current editor
423
+ // accepts schematics (frontend matrix; HQ Edge re-enforces server-side) and
424
+ // the node half resolved at least one artifact uri.
425
+ const placeSupport = placeSupportOf(props.getHqEdge, 'schematic')
426
+ const placeableCount = result.artifacts.filter((a) => a.uri).length
427
+ const canPlace = !!placeSupport?.() && placeableCount > 0
358
428
 
359
429
  return (
360
430
  <div className="hq-sch">
@@ -366,6 +436,13 @@ export const GenHit = memo(function GenHit(props: GenHitProps): ReactElement {
366
436
  <button type="button" className="hq-sch__act" onClick={onDownload} disabled={!canDownload || busy === 'download'}>
367
437
  ⭳ {busy === 'download' ? t('card.action.downloading') : t('card.action.download')}
368
438
  </button>
439
+ {canPlace
440
+ ? (
441
+ <button type="button" className="hq-sch__act" onClick={onPlace} disabled={busy === 'place'}>
442
+ ⇥ {busy === 'place' ? t('card.action.placing') : t('card.action.place')}
443
+ </button>
444
+ )
445
+ : null}
369
446
  <button type="button" className="hq-sch__act" onClick={onRegenerate} disabled={busy === 'regenerate'}>
370
447
  ↻ {busy === 'regenerate' ? t('card.action.regenerating') : t('card.action.regenerate')}
371
448
  </button>
@@ -373,6 +450,7 @@ export const GenHit = memo(function GenHit(props: GenHitProps): ReactElement {
373
450
  ? <button type="button" className="hq-sch__act" onClick={() => props.inspect?.()}>{t('card.action.inspect')}</button>
374
451
  : null}
375
452
  </div>
453
+ {placeStatus ? <div className="hq-sch__note">{placeStatus}</div> : null}
376
454
  </div>
377
455
  )
378
456
  })
@@ -45,6 +45,11 @@ const ZH = {
45
45
  'card.meta.connections': '{count} 条连接',
46
46
  'card.action.download': '下载',
47
47
  'card.action.downloading': '下载中…',
48
+ 'card.action.place': '放置到编辑器',
49
+ 'card.action.placing': '放置中…',
50
+ 'card.place.done': '已放置 {count} 个文件到编辑器',
51
+ 'card.place.partial': '已放置 {placed} 个,{failed} 个失败:',
52
+ 'card.place.failed': '放置失败:',
48
53
  'card.action.regenerate': '重新生成',
49
54
  'card.action.regenerating': '重新生成中…',
50
55
  'card.action.inspect': '详情',
@@ -95,6 +100,11 @@ const EN: Record<CopyKey, string> = {
95
100
  'card.meta.connections': '{count} connection(s)',
96
101
  'card.action.download': 'Download',
97
102
  'card.action.downloading': 'Downloading…',
103
+ 'card.action.place': 'Place in editor',
104
+ 'card.action.placing': 'Placing…',
105
+ 'card.place.done': 'Placed {count} file(s) into the editor',
106
+ 'card.place.partial': 'Placed {placed}, {failed} failed: ',
107
+ 'card.place.failed': 'Placement failed: ',
98
108
  'card.action.regenerate': 'Regenerate',
99
109
  'card.action.regenerating': 'Regenerating…',
100
110
  'card.action.inspect': 'Inspect',
@@ -17,6 +17,7 @@ import { createElement, useEffect, useState, type ComponentType } from 'react'
17
17
  import { GenHit } from './hit-card.jsx'
18
18
  import { injectStyles, removeStyles, disposeThemeObserver, installSchematicUncollapser } from './theme.js'
19
19
  import type { AuthStateLike, PromptSender } from './hit-card.jsx'
20
+ import type { HqEdgePlaceLike } from './place.js'
20
21
 
21
22
  export type { AuthStateLike, PromptSender }
22
23
 
@@ -116,6 +117,12 @@ export function apply(ctx: ClientContext): () => void {
116
117
  const authService = ctx.get<HuaqiuAuthClientService | undefined>('huaqiuAuth')
117
118
  const useAuthState = createUseAuthState(authService?.auth)
118
119
 
120
+ // Lazy `hqEdge` accessor for the Place action. NOT captured once: the
121
+ // edge-bridge plugin may load after this one, and a click should see the
122
+ // service as soon as it exists. Undefined in standalone DSH (no edge-bridge)
123
+ // — the card then simply hides Place.
124
+ const getHqEdge = () => ctx.get<HqEdgePlaceLike | undefined>('hqEdge')
125
+
119
126
  const sendPrompt: PromptSender = (sessionId, message) => {
120
127
  const sessions = ctx.get<{ binding(id: string): { session: { prompt(content: Array<{ type: 'text'; text: string }>, mode: 'queue'): Promise<unknown> } } | undefined }>('sessions')
121
128
  if (!sessions || typeof sessions.binding !== 'function' || !sessionId) {
@@ -151,6 +158,7 @@ export function apply(ctx: ClientContext): () => void {
151
158
  callId: props.callId,
152
159
  sendPrompt,
153
160
  authState: useAuthState(),
161
+ getHqEdge,
154
162
  })
155
163
 
156
164
  for (const toolName of TOOLVIEW_KEYS) {
@@ -4,12 +4,14 @@
4
4
  * Result shapes produced by the node half (`src/tools.ts`):
5
5
  * generate_schematic_from_description →
6
6
  * { status:'generated', kind:'schematic', design_name, schFiles:[{filename}],
7
- * schArtifacts:[{id,type,filename,size}], kicadPro, project_achieve_url, note? }
7
+ * schArtifacts:[{id,type,filename,size,uri?}], kicadPro, project_achieve_url, note? }
8
8
  * generate_system_module_graph →
9
9
  * { status:'generated', kind:'system', design_name, module_count,
10
10
  * connection_count, module_names, zip_bytes,
11
- * zipArtifact:{id,type:'zip',filename,size}, note? }
11
+ * zipArtifact:{id,type:'zip',filename,size,uri?}, note? }
12
12
  * both may return { status:'needs_auth', kind, hint }
13
+ *
14
+ * `uri` (when present) is the HQ Edge-resolvable handle the Place action uses.
13
15
  */
14
16
 
15
17
  export interface ContentBlockLike {
@@ -28,12 +30,21 @@ export interface ArtifactRef {
28
30
  type: string | null
29
31
  filename: string | null
30
32
  size: number | null
33
+ /**
34
+ * HQ Edge-resolvable URI (`file://`) for the artifact bytes — the handle the
35
+ * Place action passes to `hqEdge.placeArtifact`. `null` when the node half
36
+ * could not resolve it (standalone DSH, older node half): the card then
37
+ * hides Place.
38
+ */
39
+ uri: string | null
31
40
  }
32
41
 
33
42
  export interface SchResult {
34
43
  status: string | null
35
44
  kind: string | null
36
45
  artifact: ArtifactRef | null
46
+ /** Every placeable artifact (schematic sheets / the project zip). */
47
+ artifacts: ArtifactRef[]
37
48
  designName: string | null
38
49
  fileCount: number | null
39
50
  moduleCount: number | null
@@ -79,6 +90,7 @@ function artOf(a: unknown): ArtifactRef | null {
79
90
  type: typeof o.type === 'string' ? o.type : null,
80
91
  filename: typeof o.filename === 'string' ? o.filename : null,
81
92
  size: typeof o.size === 'number' ? o.size : null,
93
+ uri: typeof o.uri === 'string' && o.uri.length > 0 ? o.uri : null,
82
94
  }
83
95
  }
84
96
 
@@ -98,17 +110,24 @@ export function parseSchResult(text: string): SchResult | null {
98
110
  const kind = typeof o.kind === 'string' ? o.kind : null
99
111
 
100
112
  let artifact: ArtifactRef | null = null
113
+ const artifacts: ArtifactRef[] = []
101
114
  if (kind === 'system') {
102
115
  artifact = artOf(o.zipArtifact)
116
+ if (artifact) artifacts.push(artifact)
103
117
  } else {
104
118
  const list = Array.isArray(o.schArtifacts) ? (o.schArtifacts as unknown[]) : []
105
- artifact = list.length > 0 ? artOf(list[0]) : null
119
+ for (const entry of list) {
120
+ const ref = artOf(entry)
121
+ if (ref) artifacts.push(ref)
122
+ }
123
+ artifact = artifacts.length > 0 ? artifacts[0]! : null
106
124
  }
107
125
 
108
126
  return {
109
127
  status,
110
128
  kind,
111
129
  artifact,
130
+ artifacts,
112
131
  designName: typeof o.design_name === 'string' && o.design_name ? o.design_name : null,
113
132
  fileCount: Array.isArray(o.schFiles) ? (o.schFiles as unknown[]).length
114
133
  : (Array.isArray(o.schArtifacts) ? (o.schArtifacts as unknown[]).length : null),
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Place-action seam for the schematic/system HIT card.
3
+ *
4
+ * Thin re-export of the shared, browser-safe placement module
5
+ * (`@huaqiu/dsh-artifacts/placement`): the frontend compatibility matrix plus
6
+ * the lazy `hqEdge` service accessor. Kept as a local module so the card's
7
+ * import paths stay stable and the client bundle only ever pulls the pure
8
+ * subpath — the package root exports the cordis plugin entry, which must not
9
+ * be bundled into the browser.
10
+ *
11
+ * @module
12
+ */
13
+ export {
14
+ placeSupportOf,
15
+ type HqEdgePlaceLike,
16
+ type PlaceSupport,
17
+ } from '@huaqiu/dsh-artifacts/placement'
package/src/tools.ts CHANGED
@@ -166,6 +166,40 @@ async function createPreviewArtifact(
166
166
  return env.artifacts.create({ type, filename, content, contentEncoding })
167
167
  }
168
168
 
169
+ /**
170
+ * Best-effort cross-process URI for a stored artifact (`file://` to its
171
+ * content on disk). This is what the Place action hands to HQ Edge — an
172
+ * artifact *id* is store-local and meaningless there. Never throws: a
173
+ * generation must not fail because the URI is unavailable; the card simply
174
+ * hides Place when there is no uri.
175
+ */
176
+ async function artifactUriOf(env: SchematicGenEnv, id: string): Promise<string | null> {
177
+ try {
178
+ if (!env.artifacts || typeof env.artifacts.getDownloadUri !== 'function') return null
179
+ return await env.artifacts.getDownloadUri(id)
180
+ } catch (err) {
181
+ console.warn(LOG_TAG, 'getDownloadUri failed for', id, String((err as Error)?.message || err))
182
+ return null
183
+ }
184
+ }
185
+
186
+ /** Artifact entry shape in the tool result (parsed by the client card). */
187
+ interface ArtifactEntry {
188
+ id: string
189
+ type: string
190
+ filename: string
191
+ size: number
192
+ /** HQ Edge-resolvable URI (`file://`), present when resolvable. */
193
+ uri?: string
194
+ }
195
+
196
+ async function toArtifactEntry(env: SchematicGenEnv, created: CreateArtifactResult): Promise<ArtifactEntry> {
197
+ const entry: ArtifactEntry = { id: created.id, type: created.type, filename: created.filename, size: created.size }
198
+ const uri = await artifactUriOf(env, created.id)
199
+ if (uri) entry.uri = uri
200
+ return entry
201
+ }
202
+
169
203
  // ── Deliverable extraction ───────────────────────────────────────────────────
170
204
 
171
205
  export interface SchematicSheet {
@@ -219,7 +253,7 @@ export function extractModuleGraph(state: Record<string, unknown>): Record<strin
219
253
 
220
254
  interface MaterializedSchematic {
221
255
  schFiles: Array<{ filename: string; content?: string }>
222
- schArtifacts?: Array<{ id: string; type: string; filename: string; size: number }>
256
+ schArtifacts?: Array<ArtifactEntry>
223
257
  /** User-safe status detail — the client card renders this. */
224
258
  note?: string
225
259
  /** Agent-only explanation/directive — the client card MUST NOT render it. */
@@ -234,7 +268,7 @@ interface MaterializedSchematic {
234
268
  */
235
269
  async function materializeSchematicArtifacts(env: SchematicGenEnv, schFiles: SchematicSheet[]): Promise<MaterializedSchematic> {
236
270
  const outFiles: Array<{ filename: string; content?: string }> = schFiles.map((f) => ({ filename: f.filename }))
237
- const artifacts: Array<{ id: string; type: string; filename: string; size: number }> = []
271
+ const artifacts: ArtifactEntry[] = []
238
272
  let anyFailed = false
239
273
  let errorNote = ''
240
274
 
@@ -242,7 +276,7 @@ async function materializeSchematicArtifacts(env: SchematicGenEnv, schFiles: Sch
242
276
  const file = schFiles[i]!
243
277
  try {
244
278
  const created = await createPreviewArtifact(env, 'schematic', file.filename, file.content)
245
- artifacts.push({ id: created.id, type: created.type, filename: created.filename, size: created.size })
279
+ artifacts.push(await toArtifactEntry(env, created))
246
280
  } catch (storeErr) {
247
281
  anyFailed = true
248
282
  outFiles[i]!.content = file.content // data-loss guard
@@ -445,11 +479,11 @@ export async function runGenerateSystem(
445
479
  // Store the project zip as a `zip` preview artifact (primary). Keeping the
446
480
  // zip OUT of the JSON keeps the result small — inlining base64 used to
447
481
  // truncate the tool result and fail the card.
448
- let zipArtifact: { id: string; type: string; filename: string; size: number } | null = null
482
+ let zipArtifact: ArtifactEntry | null = null
449
483
  try {
450
484
  const safeName = sanitizeZipBaseName(designName)
451
485
  const created = await createPreviewArtifact(env, 'zip', safeName + '.zip', zipBuf.toString('base64'), 'base64')
452
- zipArtifact = { id: created.id, type: created.type, filename: created.filename, size: created.size }
486
+ zipArtifact = await toArtifactEntry(env, created)
453
487
  } catch (storeErr) {
454
488
  notes.push('Could not store the project zip as an artifact (' +
455
489
  String((storeErr as Error)?.message || storeErr) + '); kept it in the result instead.')
@@ -507,7 +541,8 @@ function createSchematicTool(env: SchematicGenEnv) {
507
541
  'regulator power supply with input and output filter capacitors". Calls the ' +
508
542
  'online HQ-EDA schematic generation agent and returns ' +
509
543
  'schFiles (filename references), schArtifacts (preview artifact references ' +
510
- 'with id/type/filename/size per sheet), kicadPro and project_achieve_url. ' +
544
+ 'with id/type/filename/size plus a uri when the cross-process placement ' +
545
+ 'channel is available), kicadPro and project_achieve_url. ' +
511
546
  'Use this when the user asks to draw, generate or create a circuit ' +
512
547
  'schematic from a description (not from an image — for that use the ' +
513
548
  'symbol/footprint tools). ' +
@@ -549,7 +584,8 @@ function createSystemTool(env: SchematicGenEnv) {
549
584
  'parts, wires the connections, and produces a module graph; the graph is ' +
550
585
  'then exported to a KiCad project zip. Returns: a zipArtifact reference ' +
551
586
  '(preview-artifact id of the full project zip — the zip is never inlined ' +
552
- 'into the conversation) and a summary (design name, module count, ' +
587
+ 'into the conversation; a uri field is included when the cross-process ' +
588
+ 'placement channel is available) and a summary (design name, module count, ' +
553
589
  'connection count, module names). Use this when the user wants a whole ' +
554
590
  'system/module-level design, not a single schematic or symbol. ' +
555
591
  'IMPORTANT: The generated system design renders automatically as a result ' +