@stackbone/cli 0.2.3 → 0.2.4

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/main.js CHANGED
@@ -2988,7 +2988,7 @@ var DEV_HMAC_SECRET = "dev-stackbone-local-emulator-hmac-secret";
2988
2988
  //#endregion
2989
2989
  //#region ../../libs/runtime/agent-emulator/src/lib/server/contract.ts
2990
2990
  init_src$14();
2991
- var STACKBONE_CLI_BUILD_VERSION = "0.2.3";
2991
+ var STACKBONE_CLI_BUILD_VERSION = "0.2.4";
2992
2992
  /**
2993
2993
  * Capabilities the local emulator advertises on `GET /api/contract`, derived
2994
2994
  * from the route registry above so the advertised set and the mounted routes
@@ -13712,7 +13712,15 @@ var init_trigger_descriptor = __esmMin((() => {
13712
13712
  /** Dot-path, per item, to the sortable marker used to compute the next cursor. */
13713
13713
  field: z.string(),
13714
13714
  /** Request arg set to the stored cursor so the provider filters server-side. */
13715
- param: z.string()
13715
+ param: z.string(),
13716
+ /**
13717
+ * Optional template for the parameter value — when present, the stored cursor
13718
+ * is interpolated into this template via `{cursor}` instead of injected as a
13719
+ * bare value. Use when the provider needs a full expression rather than a raw
13720
+ * value, e.g. `receivedDateTime gt {cursor}` for OData `$filter` params.
13721
+ * Absent = backward-compatible bare-value injection.
13722
+ */
13723
+ cursorTemplate: z.string().optional()
13716
13724
  }), z.object({
13717
13725
  kind: z.literal("opaque"),
13718
13726
  /** Dot-path in the list response to the next opaque token. */
@@ -13732,6 +13740,23 @@ var init_trigger_descriptor = __esmMin((() => {
13732
13740
  pageToken: z.object({
13733
13741
  requestParam: z.string(),
13734
13742
  responsePath: z.string()
13743
+ }).optional(),
13744
+ /**
13745
+ * Offset-based pagination via `$skip`/`$top` — an alternative to `pageToken`
13746
+ * for providers whose OpenAPI spec does not expose the page cursor (e.g.
13747
+ * Microsoft Graph, which paginates via `@odata.nextLink` URLs but only
13748
+ * exposes `$skip` as a query parameter). When present, the engine calls the
13749
+ * list operation with `pageSizeParam` set to `stride` on every request, and
13750
+ * increments `param` by `stride` each page. Stops when a page returns fewer
13751
+ * items than `stride`. Mutually exclusive with `pageToken`.
13752
+ */
13753
+ pageSkip: z.object({
13754
+ /** Query param name for the offset, e.g. `$skip`. */
13755
+ param: z.string(),
13756
+ /** Query param name for the page size, e.g. `$top`. */
13757
+ pageSizeParam: z.string(),
13758
+ /** Number of items per page, also the stride for incrementing `param`. */
13759
+ stride: z.number()
13735
13760
  }).optional()
13736
13761
  });
13737
13762
  triggerItemStepSchema = z.object({
@@ -13900,6 +13925,173 @@ var init_registry_store = __esmMin((() => {
13900
13925
  };
13901
13926
  }));
13902
13927
  //#endregion
13928
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/gmail/gmail.connector.ts
13929
+ var GMAIL_CONNECTOR;
13930
+ var init_gmail_connector = __esmMin((() => {
13931
+ GMAIL_CONNECTOR = {
13932
+ id: "gmail",
13933
+ displayName: "Gmail",
13934
+ description: "Send email and read messages from a connected Gmail account.",
13935
+ logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://mail.google.com&size=64",
13936
+ category: "email",
13937
+ protocol: "openapi",
13938
+ baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
13939
+ authMode: "oauth",
13940
+ placement: { kind: "bearer" },
13941
+ scopes: [
13942
+ "openid",
13943
+ "email",
13944
+ "profile",
13945
+ "https://www.googleapis.com/auth/gmail.readonly",
13946
+ "https://www.googleapis.com/auth/gmail.send"
13947
+ ],
13948
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
13949
+ tokenUrl: "https://oauth2.googleapis.com/token",
13950
+ authorizeParams: {
13951
+ access_type: "offline",
13952
+ prompt: "consent"
13953
+ },
13954
+ specSource: {
13955
+ kind: "url",
13956
+ url: "https://api.apis.guru/v2/specs/googleapis.com/gmail/v1/openapi.json"
13957
+ },
13958
+ triggers: [{
13959
+ id: "message-received",
13960
+ displayName: "Message received",
13961
+ description: "Fires when a new message arrives in the inbox.",
13962
+ sampleData: {
13963
+ messageId: "18e0a1b2c3d4e5f6",
13964
+ threadId: "18e0a1b2c3d4e5f6",
13965
+ from: "Ada Lovelace <ada@example.com>",
13966
+ to: "agent@stackbone.ai",
13967
+ subject: "Quarterly report",
13968
+ snippet: "Here is the quarterly report you asked for…",
13969
+ date: "Mon, 10 Jun 2024 09:15:00 +0000",
13970
+ raw: {
13971
+ messageId: "18e0a1b2c3d4e5f6",
13972
+ threadId: "18e0a1b2c3d4e5f6",
13973
+ historyId: "987654"
13974
+ }
13975
+ },
13976
+ poll: {
13977
+ kind: "strategy",
13978
+ strategy: "gmail.history"
13979
+ }
13980
+ }]
13981
+ };
13982
+ }));
13983
+ //#endregion
13984
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/outlook/outlook.connector.ts
13985
+ var OUTLOOK_CONNECTOR;
13986
+ var init_outlook_connector = __esmMin((() => {
13987
+ OUTLOOK_CONNECTOR = {
13988
+ id: "outlook",
13989
+ displayName: "Outlook",
13990
+ description: "Send email and read messages from a connected Outlook account.",
13991
+ logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://microsoft.com&size=64",
13992
+ category: "email",
13993
+ protocol: "openapi",
13994
+ baseUrl: "https://graph.microsoft.com/v1.0",
13995
+ authMode: "oauth",
13996
+ placement: { kind: "bearer" },
13997
+ scopes: [
13998
+ "openid",
13999
+ "email",
14000
+ "profile",
14001
+ "Mail.Read",
14002
+ "Mail.Send",
14003
+ "offline_access"
14004
+ ],
14005
+ authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
14006
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
14007
+ specSource: {
14008
+ kind: "url",
14009
+ url: "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/v1.0/default.yaml"
14010
+ },
14011
+ authorizeParams: { prompt: "consent" },
14012
+ triggers: [{
14013
+ id: "message-received",
14014
+ displayName: "Message received",
14015
+ description: "Fires when a new message arrives in the Inbox.",
14016
+ sampleData: {
14017
+ messageId: "AAMkAD...",
14018
+ conversationId: "AAQkAD...",
14019
+ from: {
14020
+ name: "Ada Lovelace",
14021
+ address: "ada@example.com"
14022
+ },
14023
+ subject: "Quarterly report",
14024
+ snippet: "Here is the quarterly report...",
14025
+ date: "2024-06-10T09:15:00Z",
14026
+ isRead: false
14027
+ },
14028
+ poll: {
14029
+ kind: "declarative",
14030
+ list: {
14031
+ operationId: "me.mailFolders.ListMessages",
14032
+ args: { "mailFolder-id": "Inbox" },
14033
+ itemsPath: "value",
14034
+ pageSkip: {
14035
+ param: "$skip",
14036
+ pageSizeParam: "$top",
14037
+ stride: 100
14038
+ }
14039
+ },
14040
+ cursor: {
14041
+ kind: "timestamp",
14042
+ field: "receivedDateTime",
14043
+ param: "$filter",
14044
+ cursorTemplate: "receivedDateTime gt {cursor}"
14045
+ },
14046
+ dedup: { idPath: "id" },
14047
+ map: {
14048
+ messageId: "id",
14049
+ conversationId: "conversationId",
14050
+ from: "from",
14051
+ subject: "subject",
14052
+ snippet: "bodyPreview",
14053
+ date: "receivedDateTime",
14054
+ isRead: "isRead"
14055
+ }
14056
+ }
14057
+ }]
14058
+ };
14059
+ }));
14060
+ //#endregion
14061
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/telegram/telegram.connector.ts
14062
+ var TELEGRAM_CONNECTOR;
14063
+ var init_telegram_connector = __esmMin((() => {
14064
+ TELEGRAM_CONNECTOR = {
14065
+ id: "telegram",
14066
+ displayName: "Telegram",
14067
+ description: "Send and receive messages through a Telegram bot.",
14068
+ logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://telegram.org/&size=64",
14069
+ category: "messaging",
14070
+ protocol: "openapi",
14071
+ baseUrl: "https://api.telegram.org",
14072
+ authMode: "api_key",
14073
+ placement: { kind: "bearer" },
14074
+ scopes: []
14075
+ };
14076
+ }));
14077
+ //#endregion
14078
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/whatsapp/whatsapp.connector.ts
14079
+ var WHATSAPP_CONNECTOR;
14080
+ var init_whatsapp_connector = __esmMin((() => {
14081
+ WHATSAPP_CONNECTOR = {
14082
+ id: "whatsapp",
14083
+ displayName: "WhatsApp",
14084
+ description: "Send and receive messages through a WhatsApp bot.",
14085
+ logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://www.whatsapp.com/&size=64",
14086
+ category: "messaging",
14087
+ protocol: "openapi",
14088
+ baseUrl: "https://api.whatsapp.com/v1",
14089
+ authMode: "api_key",
14090
+ placement: { kind: "bearer" },
14091
+ scopes: []
14092
+ };
14093
+ }));
14094
+ //#endregion
13903
14095
  //#region ../../libs/shared/connect-registry/src/lib/catalog/built-in-catalog.ts
13904
14096
  /**
13905
14097
  * Seed `store` with the built-in catalog. Returns `loadFromJson`'s outcome so the
@@ -13911,91 +14103,15 @@ function loadBuiltInCatalog(store) {
13911
14103
  }
13912
14104
  var BUILT_IN_CONNECTOR_DOCS;
13913
14105
  var init_built_in_catalog = __esmMin((() => {
14106
+ init_gmail_connector();
14107
+ init_outlook_connector();
14108
+ init_telegram_connector();
14109
+ init_whatsapp_connector();
13914
14110
  BUILT_IN_CONNECTOR_DOCS = [
13915
- {
13916
- id: "gmail",
13917
- displayName: "Gmail",
13918
- description: "Send email and read messages from a connected Gmail account.",
13919
- logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://mail.google.com&size=64",
13920
- category: "email",
13921
- protocol: "openapi",
13922
- baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
13923
- authMode: "oauth",
13924
- placement: { kind: "bearer" },
13925
- scopes: [
13926
- "openid",
13927
- "email",
13928
- "profile",
13929
- "https://www.googleapis.com/auth/gmail.readonly",
13930
- "https://www.googleapis.com/auth/gmail.send"
13931
- ],
13932
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
13933
- tokenUrl: "https://oauth2.googleapis.com/token",
13934
- authorizeParams: {
13935
- access_type: "offline",
13936
- prompt: "consent"
13937
- },
13938
- specSource: {
13939
- kind: "url",
13940
- url: "https://api.apis.guru/v2/specs/googleapis.com/gmail/v1/openapi.json"
13941
- },
13942
- triggers: [{
13943
- id: "message-received",
13944
- displayName: "Message received",
13945
- description: "Fires when a new message arrives in the inbox.",
13946
- sampleData: {
13947
- messageId: "18e0a1b2c3d4e5f6",
13948
- threadId: "18e0a1b2c3d4e5f6",
13949
- from: "Ada Lovelace <ada@example.com>",
13950
- to: "agent@stackbone.ai",
13951
- subject: "Quarterly report",
13952
- snippet: "Here is the quarterly report you asked for…",
13953
- date: "Mon, 10 Jun 2024 09:15:00 +0000",
13954
- raw: {
13955
- messageId: "18e0a1b2c3d4e5f6",
13956
- threadId: "18e0a1b2c3d4e5f6",
13957
- historyId: "987654"
13958
- }
13959
- },
13960
- poll: {
13961
- kind: "strategy",
13962
- strategy: "gmail.history"
13963
- }
13964
- }]
13965
- },
13966
- {
13967
- id: "outlook",
13968
- displayName: "Outlook",
13969
- description: "Send email and read messages from a connected Outlook account.",
13970
- logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://microsoft.com&size=64",
13971
- category: "email",
13972
- protocol: "openapi",
13973
- baseUrl: "https://graph.microsoft.com/v1.0",
13974
- authMode: "oauth",
13975
- placement: { kind: "bearer" },
13976
- scopes: [
13977
- "openid",
13978
- "email",
13979
- "profile",
13980
- "Mail.Read",
13981
- "Mail.Send",
13982
- "offline_access"
13983
- ],
13984
- authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
13985
- tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token"
13986
- },
13987
- {
13988
- id: "telegram",
13989
- displayName: "Telegram",
13990
- description: "Send and receive messages through a Telegram bot.",
13991
- logoUrl: "https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://telegram.org/&size=64",
13992
- category: "messaging",
13993
- protocol: "openapi",
13994
- baseUrl: "https://api.telegram.org",
13995
- authMode: "api_key",
13996
- placement: { kind: "bearer" },
13997
- scopes: []
13998
- }
14111
+ GMAIL_CONNECTOR,
14112
+ OUTLOOK_CONNECTOR,
14113
+ TELEGRAM_CONNECTOR,
14114
+ WHATSAPP_CONNECTOR
13999
14115
  ];
14000
14116
  }));
14001
14117
  //#endregion
@@ -14568,14 +14684,17 @@ function latestTimestamp(items, field) {
14568
14684
  function createDeclarativePoll(spec) {
14569
14685
  return async (ctx, cursor) => {
14570
14686
  const baseArgs = { ...spec.list.args ?? {} };
14571
- if (cursor !== null && cursor !== void 0) if (spec.cursor.kind === "timestamp") baseArgs[spec.cursor.param] = cursor;
14687
+ if (cursor !== null && cursor !== void 0) if (spec.cursor.kind === "timestamp") baseArgs[spec.cursor.param] = spec.cursor.cursorTemplate ? spec.cursor.cursorTemplate.replace("{cursor}", String(cursor)) : cursor;
14572
14688
  else baseArgs[spec.cursor.requestParam] = cursor;
14573
14689
  const rawItems = [];
14574
14690
  let opaqueNext;
14575
14691
  let pageToken;
14576
14692
  for (let page = 0; page < MAX_PAGES; page += 1) {
14577
14693
  const args = { ...baseArgs };
14578
- if (pageToken !== null && pageToken !== void 0 && spec.list.pageToken) args[spec.list.pageToken.requestParam] = pageToken;
14694
+ if (spec.list.pageSkip) {
14695
+ args[spec.list.pageSkip.pageSizeParam] = spec.list.pageSkip.stride;
14696
+ if (page > 0) args[spec.list.pageSkip.param] = page * spec.list.pageSkip.stride;
14697
+ } else if (pageToken !== null && pageToken !== void 0 && spec.list.pageToken) args[spec.list.pageToken.requestParam] = pageToken;
14579
14698
  const response = await ctx.runOperation(spec.list.operationId, args);
14580
14699
  const items = getPath(response, spec.list.itemsPath);
14581
14700
  if (Array.isArray(items)) rawItems.push(...items);
@@ -14583,8 +14702,12 @@ function createDeclarativePoll(spec) {
14583
14702
  const next = getPath(response, spec.cursor.responsePath);
14584
14703
  if (next !== null && next !== void 0) opaqueNext = next;
14585
14704
  }
14586
- pageToken = spec.list.pageToken ? getPath(response, spec.list.pageToken.responsePath) : void 0;
14587
- if (pageToken === null || pageToken === void 0) break;
14705
+ if (spec.list.pageSkip) {
14706
+ if (!Array.isArray(items) || items.length < spec.list.pageSkip.stride) break;
14707
+ } else {
14708
+ pageToken = spec.list.pageToken ? getPath(response, spec.list.pageToken.responsePath) : void 0;
14709
+ if (pageToken === null || pageToken === void 0) break;
14710
+ }
14588
14711
  }
14589
14712
  let items = rawItems;
14590
14713
  if (spec.item) {
@@ -14610,6 +14733,113 @@ var init_declarative_poll = __esmMin((() => {
14610
14733
  MAX_PAGES = 100;
14611
14734
  }));
14612
14735
  //#endregion
14736
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/gmail/gmail.history-strategy.ts
14737
+ function isGmailCursor(cursor) {
14738
+ return typeof cursor === "object" && cursor !== null && typeof cursor.historyId === "string" && cursor.historyId.length > 0;
14739
+ }
14740
+ /** Read a single header value (case-insensitive) off a Gmail message payload. */
14741
+ function header(message, name) {
14742
+ const match = (message.payload?.headers ?? []).find((h) => (h.name ?? "").toLowerCase() === name.toLowerCase());
14743
+ return typeof match?.value === "string" ? match.value : "";
14744
+ }
14745
+ /** Fetch one message (metadata format) and normalize it to a poll item. */
14746
+ async function fetchMessageEvent(ctx, id) {
14747
+ const message = await ctx.runOperation("gmail.users.messages.get", {
14748
+ userId: USER_ID,
14749
+ id,
14750
+ format: "metadata"
14751
+ });
14752
+ const payload = {
14753
+ messageId: message.id ?? id,
14754
+ threadId: message.threadId ?? "",
14755
+ from: header(message, "From"),
14756
+ to: header(message, "To"),
14757
+ subject: header(message, "Subject"),
14758
+ snippet: typeof message.snippet === "string" ? message.snippet : "",
14759
+ date: header(message, "Date"),
14760
+ raw: {
14761
+ messageId: message.id ?? id,
14762
+ threadId: message.threadId ?? "",
14763
+ historyId: typeof message.historyId === "string" ? message.historyId : null
14764
+ }
14765
+ };
14766
+ return {
14767
+ id: message.id ?? id,
14768
+ payload
14769
+ };
14770
+ }
14771
+ function readHistoryId(event) {
14772
+ const raw = (event?.payload)?.raw;
14773
+ return raw && typeof raw.historyId === "string" ? raw.historyId : null;
14774
+ }
14775
+ /**
14776
+ * Bootstrap path (empty cursor): list the most recent inbox messages, normalize
14777
+ * them, and seed the cursor from the newest message's `historyId`. Dedup is the
14778
+ * platform's job (by message id), so a first poll legitimately returns the recent
14779
+ * window and the next poll resumes from the history API.
14780
+ */
14781
+ async function bootstrap(ctx) {
14782
+ const items = await mapWithConcurrency(((await ctx.runOperation("gmail.users.messages.list", {
14783
+ userId: USER_ID,
14784
+ maxResults: BOOTSTRAP_MAX_RESULTS,
14785
+ labelIds: "INBOX"
14786
+ })).messages ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0), 6, (id) => fetchMessageEvent(ctx, id));
14787
+ const seedHistoryId = readHistoryId(items[0]);
14788
+ return {
14789
+ items,
14790
+ nextCursor: seedHistoryId ? { historyId: seedHistoryId } : null
14791
+ };
14792
+ }
14793
+ /**
14794
+ * Incremental path (valid cursor): pull history records added since the persisted
14795
+ * `historyId` via `history.list`, drain EVERY page (so messages on pages 2+ are
14796
+ * never skipped when the cursor advances), normalize each new arrival, and move
14797
+ * the cursor to the response's latest `historyId`.
14798
+ */
14799
+ async function pollFromCursor(ctx, cursor) {
14800
+ const addedIds = [];
14801
+ let latestHistoryId = cursor.historyId;
14802
+ let pageToken;
14803
+ for (let page = 0; page < MAX_HISTORY_PAGES; page += 1) {
14804
+ const history = await ctx.runOperation("gmail.users.history.list", {
14805
+ userId: USER_ID,
14806
+ startHistoryId: cursor.historyId,
14807
+ historyTypes: "messageAdded",
14808
+ labelId: "INBOX",
14809
+ ...pageToken ? { pageToken } : {}
14810
+ });
14811
+ for (const record of history.history ?? []) for (const added of record.messagesAdded ?? []) {
14812
+ const id = added.message?.id;
14813
+ if (typeof id === "string" && id.length > 0) addedIds.push(id);
14814
+ }
14815
+ if (typeof history.historyId === "string" && history.historyId.length > 0) latestHistoryId = history.historyId;
14816
+ if (typeof history.nextPageToken === "string" && history.nextPageToken.length > 0) pageToken = history.nextPageToken;
14817
+ else break;
14818
+ }
14819
+ return {
14820
+ items: await mapWithConcurrency(addedIds, 6, (id) => fetchMessageEvent(ctx, id)),
14821
+ nextCursor: { historyId: latestHistoryId }
14822
+ };
14823
+ }
14824
+ var USER_ID, BOOTSTRAP_MAX_RESULTS, MAX_HISTORY_PAGES, gmailHistoryPoll;
14825
+ var init_gmail_history_strategy = __esmMin((() => {
14826
+ init_concurrency();
14827
+ USER_ID = "me";
14828
+ BOOTSTRAP_MAX_RESULTS = 25;
14829
+ MAX_HISTORY_PAGES = 100;
14830
+ gmailHistoryPoll = async (ctx, cursor) => {
14831
+ if (!isGmailCursor(cursor)) return bootstrap(ctx);
14832
+ return pollFromCursor(ctx, cursor);
14833
+ };
14834
+ }));
14835
+ //#endregion
14836
+ //#region ../../libs/shared/connect-registry/src/lib/catalog/built-in-strategies.ts
14837
+ var BUILT_IN_STRATEGIES;
14838
+ var init_built_in_strategies = __esmMin((() => {
14839
+ init_gmail_history_strategy();
14840
+ BUILT_IN_STRATEGIES = { "gmail.history": gmailHistoryPoll };
14841
+ }));
14842
+ //#endregion
14613
14843
  //#region ../../libs/shared/connect-registry/src/index.ts
14614
14844
  var init_src$3 = __esmMin((() => {
14615
14845
  init_types$1();
@@ -14622,6 +14852,7 @@ var init_src$3 = __esmMin((() => {
14622
14852
  init_trigger_descriptor();
14623
14853
  init_declarative_poll();
14624
14854
  init_concurrency();
14855
+ init_built_in_strategies();
14625
14856
  }));
14626
14857
  //#endregion
14627
14858
  //#region ../../libs/platform/connect-broker/src/lib/executor/spec-introspection.ts
@@ -16695,8 +16926,14 @@ function invalidRequest(message) {
16695
16926
  }
16696
16927
  //#endregion
16697
16928
  //#region ../../libs/runtime/agent-emulator/src/lib/deep-agents/registry.ts
16698
- var DEEP_AGENTS_WELL_KNOWN_DIR, isPlainRecord, readConfigStrings, toMetadata, isDeepAgentDefinition, bundleAndLoad, loadPrebuilt, createDeepAgentRegistry, errMessage$9, matchChangedPathsToAgents;
16929
+ var SYSTEM_DEEP_AGENTS, isInternalDeepAgent, DEEP_AGENTS_WELL_KNOWN_DIR, isPlainRecord, readConfigStrings, toMetadata, isDeepAgentDefinition, bundleAndLoad, loadPrebuilt, createDeepAgentRegistry, errMessage$9, matchChangedPathsToAgents;
16699
16930
  var init_registry$1 = __esmMin((() => {
16931
+ SYSTEM_DEEP_AGENTS = new Set(["rag-ingest"]);
16932
+ isInternalDeepAgent = (agent) => {
16933
+ if (agent.$internal === false) return false;
16934
+ if (agent.$internal === true) return true;
16935
+ return SYSTEM_DEEP_AGENTS.has(agent.name);
16936
+ };
16700
16937
  DEEP_AGENTS_WELL_KNOWN_DIR = ".well-known/deep-agents";
16701
16938
  isPlainRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
16702
16939
  readConfigStrings = (config) => {
@@ -16788,7 +17025,8 @@ var init_registry$1 = __esmMin((() => {
16788
17025
  metadata: toMetadata(def)
16789
17026
  },
16790
17027
  createGraph: def.createGraph.bind(def),
16791
- supportsCheckpointer: def.capabilities?.checkpointer === true
17028
+ supportsCheckpointer: def.capabilities?.checkpointer === true,
17029
+ $internal: isInternalDeepAgent(agent)
16792
17030
  });
16793
17031
  logger.info(`deep agent "${agent.name}" ready (model ${def.metadata.model}).`);
16794
17032
  return true;
@@ -16829,7 +17067,7 @@ var init_registry$1 = __esmMin((() => {
16829
17067
  return {
16830
17068
  list: () => {
16831
17069
  const checkpointerReady = opts.checkpointer?.status?.() === "ready";
16832
- return [...registered.values()].map((e) => ({
17070
+ return [...registered.values()].filter((e) => !e.$internal).map((e) => ({
16833
17071
  name: e.view.name,
16834
17072
  metadata: e.view.metadata,
16835
17073
  durable: e.supportsCheckpointer && checkpointerReady
@@ -22240,13 +22478,19 @@ var init_workflow_step_world_port = __esmMin((() => {
22240
22478
  }));
22241
22479
  //#endregion
22242
22480
  //#region ../../libs/runtime/agent-emulator/src/lib/workflow/registry.ts
22243
- var hasDeclaredSchema, workflowStartTrigger, buildManifest;
22481
+ var hasDeclaredSchema, workflowStartTrigger, SYSTEM_WORKFLOWS, isInternalWorkflow, buildManifest;
22244
22482
  var init_registry = __esmMin((() => {
22245
22483
  hasDeclaredSchema = (pair) => pair != null && (pair.input != null || pair.output != null);
22246
22484
  workflowStartTrigger = (name) => `POST /api/workflows/${name}/start`;
22485
+ SYSTEM_WORKFLOWS = new Set(["rag-ingest"]);
22486
+ isInternalWorkflow = (workflow) => {
22487
+ if (workflow.$internal === false) return false;
22488
+ if (workflow.$internal === true) return true;
22489
+ return SYSTEM_WORKFLOWS.has(workflow.name);
22490
+ };
22247
22491
  buildManifest = (input) => ({
22248
22492
  agents: [],
22249
- workflows: input.workspace.workflows.map((w) => {
22493
+ workflows: input.workspace.workflows.filter((w) => !isInternalWorkflow(w)).map((w) => {
22250
22494
  const steps = input.workflowSteps?.get(w.name);
22251
22495
  return {
22252
22496
  name: w.name,
@@ -22488,6 +22732,28 @@ var init_workflow_start_service = __esmMin((() => {
22488
22732
  return triggerWorkflowStartAndWait(deps, name, input);
22489
22733
  }
22490
22734
  });
22735
+ }));
22736
+ //#endregion
22737
+ //#region ../../libs/runtime/agent-emulator/src/lib/workflow/approval-recorder-bridge.ts
22738
+ var createApprovalRecorder;
22739
+ var init_approval_recorder_bridge = __esmMin((() => {
22740
+ createApprovalRecorder = (deps) => ({ async request(args) {
22741
+ const context = deps.getInvocationContext?.();
22742
+ const enriched = { ...args };
22743
+ if (context?.runId !== void 0) enriched["runId"] = context.runId;
22744
+ if (context?.requestedByStepId !== void 0) enriched["requestedByStepId"] = context.requestedByStepId;
22745
+ try {
22746
+ const result = await deps.request(enriched);
22747
+ if (result.error) {
22748
+ const code = result.error.code ?? "approval_persist_failed";
22749
+ const message = result.error.message ?? "Failed to write the approval to the agent database.";
22750
+ throw new Error(`requestApproval could not persist the approvals row (${code}): ${message} The workflow step will retry — a HITL decision is never quietly lost.`);
22751
+ }
22752
+ } catch (err) {
22753
+ console.error("[approval-recorder] failed to persist approvals row:", err instanceof Error ? err.message : String(err));
22754
+ throw err;
22755
+ }
22756
+ } });
22491
22757
  })), DEFAULT_TZ, TICK_JOB_NAME$1, DECL_PREFIX, DYN_PREFIX, declSchedulerId, dynSchedulerId, planDeclarativeReconcile, expandDeclaredSchedules, parseSchedulerId, consoleLogger$2, WorkflowCronScheduler, createWorkflowSchedulerBridge;
22492
22758
  var init_workflow_cron_scheduler = __esmMin((() => {
22493
22759
  DEFAULT_TZ = "UTC";
@@ -23057,111 +23323,11 @@ var init_intake_scheduler = __esmMin((() => {
23057
23323
  };
23058
23324
  }));
23059
23325
  //#endregion
23060
- //#region ../../libs/runtime/agent-emulator/src/lib/connect-inbound/gmail-history-strategy.ts
23061
- function isGmailCursor(cursor) {
23062
- return typeof cursor === "object" && cursor !== null && typeof cursor.historyId === "string" && cursor.historyId.length > 0;
23063
- }
23064
- /** Read a single header value (case-insensitive) off a Gmail message payload. */
23065
- function header(message, name) {
23066
- const match = (message.payload?.headers ?? []).find((h) => (h.name ?? "").toLowerCase() === name.toLowerCase());
23067
- return typeof match?.value === "string" ? match.value : "";
23068
- }
23069
- /** Fetch one message (metadata format) and normalize it to a poll item. */
23070
- async function fetchMessageEvent(ctx, id) {
23071
- const message = await ctx.runOperation("gmail.users.messages.get", {
23072
- userId: USER_ID,
23073
- id,
23074
- format: "metadata"
23075
- });
23076
- const payload = {
23077
- messageId: message.id ?? id,
23078
- threadId: message.threadId ?? "",
23079
- from: header(message, "From"),
23080
- to: header(message, "To"),
23081
- subject: header(message, "Subject"),
23082
- snippet: typeof message.snippet === "string" ? message.snippet : "",
23083
- date: header(message, "Date"),
23084
- raw: {
23085
- messageId: message.id ?? id,
23086
- threadId: message.threadId ?? "",
23087
- historyId: typeof message.historyId === "string" ? message.historyId : null
23088
- }
23089
- };
23090
- return {
23091
- id: message.id ?? id,
23092
- payload
23093
- };
23094
- }
23095
- function readHistoryId(event) {
23096
- const raw = (event?.payload)?.raw;
23097
- return raw && typeof raw.historyId === "string" ? raw.historyId : null;
23098
- }
23099
- /**
23100
- * Bootstrap path (empty cursor): list the most recent inbox messages, normalize
23101
- * them, and seed the cursor from the newest message's `historyId`. Dedup is the
23102
- * platform's job (by message id), so a first poll legitimately returns the recent
23103
- * window and the next poll resumes from the history API.
23104
- */
23105
- async function bootstrap(ctx) {
23106
- const items = await mapWithConcurrency(((await ctx.runOperation("gmail.users.messages.list", {
23107
- userId: USER_ID,
23108
- maxResults: BOOTSTRAP_MAX_RESULTS,
23109
- labelIds: "INBOX"
23110
- })).messages ?? []).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0), 6, (id) => fetchMessageEvent(ctx, id));
23111
- const seedHistoryId = readHistoryId(items[0]);
23112
- return {
23113
- items,
23114
- nextCursor: seedHistoryId ? { historyId: seedHistoryId } : null
23115
- };
23116
- }
23117
- /**
23118
- * Incremental path (valid cursor): pull history records added since the persisted
23119
- * `historyId` via `history.list`, drain EVERY page (so messages on pages 2+ are
23120
- * never skipped when the cursor advances), normalize each new arrival, and move
23121
- * the cursor to the response's latest `historyId`.
23122
- */
23123
- async function pollFromCursor(ctx, cursor) {
23124
- const addedIds = [];
23125
- let latestHistoryId = cursor.historyId;
23126
- let pageToken;
23127
- for (let page = 0; page < MAX_HISTORY_PAGES; page += 1) {
23128
- const history = await ctx.runOperation("gmail.users.history.list", {
23129
- userId: USER_ID,
23130
- startHistoryId: cursor.historyId,
23131
- historyTypes: "messageAdded",
23132
- labelId: "INBOX",
23133
- ...pageToken ? { pageToken } : {}
23134
- });
23135
- for (const record of history.history ?? []) for (const added of record.messagesAdded ?? []) {
23136
- const id = added.message?.id;
23137
- if (typeof id === "string" && id.length > 0) addedIds.push(id);
23138
- }
23139
- if (typeof history.historyId === "string" && history.historyId.length > 0) latestHistoryId = history.historyId;
23140
- if (typeof history.nextPageToken === "string" && history.nextPageToken.length > 0) pageToken = history.nextPageToken;
23141
- else break;
23142
- }
23143
- return {
23144
- items: await mapWithConcurrency(addedIds, 6, (id) => fetchMessageEvent(ctx, id)),
23145
- nextCursor: { historyId: latestHistoryId }
23146
- };
23147
- }
23148
- var USER_ID, BOOTSTRAP_MAX_RESULTS, MAX_HISTORY_PAGES, gmailHistoryPoll;
23149
- var init_gmail_history_strategy = __esmMin((() => {
23150
- init_src$3();
23151
- USER_ID = "me";
23152
- BOOTSTRAP_MAX_RESULTS = 25;
23153
- MAX_HISTORY_PAGES = 100;
23154
- gmailHistoryPoll = async (ctx, cursor) => {
23155
- if (!isGmailCursor(cursor)) return bootstrap(ctx);
23156
- return pollFromCursor(ctx, cursor);
23157
- };
23158
- }));
23159
- //#endregion
23160
23326
  //#region ../../libs/runtime/agent-emulator/src/lib/connect-inbound/trigger-strategies.ts
23161
23327
  var STRATEGIES, resolveStrategy;
23162
23328
  var init_trigger_strategies = __esmMin((() => {
23163
- init_gmail_history_strategy();
23164
- STRATEGIES = { "gmail.history": gmailHistoryPoll };
23329
+ init_src$3();
23330
+ STRATEGIES = { ...BUILT_IN_STRATEGIES };
23165
23331
  resolveStrategy = (name) => STRATEGIES[name] ?? null;
23166
23332
  }));
23167
23333
  //#endregion
@@ -23392,6 +23558,7 @@ var init_runtime_mount = __esmMin((() => {
23392
23558
  init_workflow_run_projector();
23393
23559
  init_workflow_invoker();
23394
23560
  init_workflow_start_service();
23561
+ init_approval_recorder_bridge();
23395
23562
  init_workflow_cron_scheduler();
23396
23563
  init_workflow_hook_resume();
23397
23564
  init_workflow_frame_sse();
@@ -23471,6 +23638,11 @@ var init_runtime_mount = __esmMin((() => {
23471
23638
  let setWorkflowStarter;
23472
23639
  let setWorkflowScheduler;
23473
23640
  let setDeepAgentInvoker;
23641
+ let setAiUsageReporter;
23642
+ let setApprovalRecorder;
23643
+ let approvalRequest;
23644
+ let setConnectorInvoker;
23645
+ let connectorInvoker;
23474
23646
  try {
23475
23647
  const sdk = await importProject("@stackbone/sdk");
23476
23648
  runWithInvocationContext = sdk.runWithInvocationContext;
@@ -23478,12 +23650,34 @@ var init_runtime_mount = __esmMin((() => {
23478
23650
  setWorkflowStarter = sdk.setWorkflowStarter;
23479
23651
  setWorkflowScheduler = sdk.setWorkflowScheduler;
23480
23652
  setDeepAgentInvoker = sdk.setDeepAgentInvoker;
23653
+ setAiUsageReporter = sdk.setAiUsageReporter;
23654
+ setApprovalRecorder = sdk.setApprovalRecorder;
23655
+ setConnectorInvoker = sdk.setConnectorInvoker;
23656
+ connectorInvoker = sdk.createConnectInvoker?.();
23657
+ const ambientStackbone = sdk.stackbone;
23658
+ if (ambientStackbone) approvalRequest = async (args) => {
23659
+ const approval = ambientStackbone.approval;
23660
+ if (!approval?.request) return {
23661
+ data: null,
23662
+ error: {
23663
+ code: "approval_unavailable",
23664
+ message: "The project SDK exposes no ambient approval writer."
23665
+ }
23666
+ };
23667
+ return approval.request(args);
23668
+ };
23669
+ else approvalRequest = void 0;
23481
23670
  } catch {
23482
23671
  runWithInvocationContext = void 0;
23483
23672
  getInvocationContext = void 0;
23484
23673
  setWorkflowStarter = void 0;
23485
23674
  setWorkflowScheduler = void 0;
23486
23675
  setDeepAgentInvoker = void 0;
23676
+ setAiUsageReporter = void 0;
23677
+ setApprovalRecorder = void 0;
23678
+ approvalRequest = void 0;
23679
+ setConnectorInvoker = void 0;
23680
+ connectorInvoker = void 0;
23487
23681
  }
23488
23682
  const stepUrl = pathToFileURL(resolve(projectRoot, WELL_KNOWN_DIR$2, "step.mjs")).href;
23489
23683
  await (runtimeEnv.generation ? import(`${stepUrl}?v=${runtimeEnv.generation}`) : import(stepUrl));
@@ -23495,7 +23689,12 @@ var init_runtime_mount = __esmMin((() => {
23495
23689
  resumeHook,
23496
23690
  setWorkflowStarter,
23497
23691
  setWorkflowScheduler,
23498
- setDeepAgentInvoker
23692
+ setDeepAgentInvoker,
23693
+ setAiUsageReporter,
23694
+ setApprovalRecorder,
23695
+ approvalRequest,
23696
+ setConnectorInvoker,
23697
+ connectorInvoker
23499
23698
  };
23500
23699
  };
23501
23700
  mountWorkflowRuntime = async (app, opts) => {
@@ -23581,6 +23780,21 @@ var init_runtime_mount = __esmMin((() => {
23581
23780
  return invoker(name, input, options);
23582
23781
  });
23583
23782
  }
23783
+ if (rt.setAiUsageReporter) {
23784
+ const invocations = opts.invocations;
23785
+ rt.setAiUsageReporter((usage) => {
23786
+ const runId = rt.getInvocationContext?.()?.runId;
23787
+ if (!runId || !invocations) return;
23788
+ invocations.addRunTokens(runId, usage).catch((err) => {
23789
+ console.warn(`[ai-usage] token fold failed for run ${runId}: ${err instanceof Error ? err.message : String(err)}`);
23790
+ });
23791
+ });
23792
+ }
23793
+ if (rt.setApprovalRecorder && rt.approvalRequest) rt.setApprovalRecorder(createApprovalRecorder({
23794
+ request: rt.approvalRequest,
23795
+ ...rt.getInvocationContext ? { getInvocationContext: rt.getInvocationContext } : {}
23796
+ }));
23797
+ if (rt.setConnectorInvoker && rt.connectorInvoker) rt.setConnectorInvoker(rt.connectorInvoker);
23584
23798
  return rt;
23585
23799
  }).catch((err) => {
23586
23800
  runtimePromise = null;
@@ -23811,7 +24025,11 @@ var init_runtime_mount = __esmMin((() => {
23811
24025
  await cronScheduler?.close();
23812
24026
  await intakeRuntime?.close();
23813
24027
  if (runtimePromise) try {
23814
- (await runtimePromise).setDeepAgentInvoker?.(void 0);
24028
+ const rt = await runtimePromise;
24029
+ rt.setDeepAgentInvoker?.(void 0);
24030
+ rt.setAiUsageReporter?.(void 0);
24031
+ rt.setApprovalRecorder?.(void 0);
24032
+ rt.setConnectorInvoker?.(void 0);
23815
24033
  } catch {}
23816
24034
  },
23817
24035
  triggerWorkflowStart: (name, input) => triggerWorkflowStart(startDeps, name, input),
@@ -30830,6 +31048,62 @@ var findWorkflowFiles = async (projectRoot, extraModules = []) => {
30830
31048
  return files;
30831
31049
  };
30832
31050
  /**
31051
+ * Scan the final `workflow.vm.js` text for any `require(...)` call and throw an
31052
+ * actionable error if one is found.
31053
+ *
31054
+ * The workflow VM bundle runs on `node:vm`, which has NO `require`. A `require(`
31055
+ * token surviving into the bundle means something reachable from a workflow body
31056
+ * (a helper, a transitive import) dragged a Node-only dependency into the VM graph
31057
+ * — the Workflow SDK (WDK) compiler inlines the whole module, and the inlined
31058
+ * `require()` throws `ReferenceError: require is not defined` at instantiation,
31059
+ * crashing EVERY workflow in the project with an opaque `USER_ERROR` / "Unknown
31060
+ * error" (the bug this guard makes impossible to ship silently).
31061
+ *
31062
+ * Pure + exported so it is unit-testable in isolation (prior art:
31063
+ * `foldSchemasIntoManifest`). False-positive discipline: it matches `require(`
31064
+ * only as a CALL shape, not the substring inside a comment or a string literal
31065
+ * that is not actually invoked — the VM bundle is generated code, so a literal
31066
+ * `require(` in a string is still a smell worth flagging, but the guard reports
31067
+ * the surrounding context so a human can tell a real leak from a benign mention.
31068
+ */
31069
+ var assertNoLeakedNodeRequire = (bundleText) => {
31070
+ const requirePattern = /require\s*\(\s*['"`]/g;
31071
+ const matches = [];
31072
+ const lines = bundleText.split("\n");
31073
+ for (const match of bundleText.matchAll(requirePattern)) {
31074
+ const index = match.index ?? 0;
31075
+ let line = 1;
31076
+ let column = 1;
31077
+ let consumed = 0;
31078
+ for (let i = 0; i < lines.length; i += 1) {
31079
+ const lineText = lines[i];
31080
+ if (lineText === void 0) break;
31081
+ const lineEnd = consumed + lineText.length + 1;
31082
+ if (index < lineEnd) {
31083
+ line = i + 1;
31084
+ column = index - consumed + 1;
31085
+ break;
31086
+ }
31087
+ consumed = lineEnd;
31088
+ }
31089
+ const start = Math.max(0, index - 80);
31090
+ const end = Math.min(bundleText.length, index + 120);
31091
+ const snippet = bundleText.slice(start, end).replace(/\s+/g, " ").trim();
31092
+ matches.push({
31093
+ index,
31094
+ line,
31095
+ column,
31096
+ snippet
31097
+ });
31098
+ }
31099
+ if (matches.length === 0) return;
31100
+ const first = matches[0];
31101
+ if (!first) return;
31102
+ const listing = matches.slice(0, 5).map((m, i) => ` ${i + 1}. line ${m.line}:${m.column} …${m.snippet}…`).join("\n");
31103
+ const more = matches.length > 5 ? `\n (and ${matches.length - 5} more)` : "";
31104
+ throw new Error(`Workflow VM bundle contains ${matches.length} require() call(s) — the workflow VM (node:vm) has no \`require\`, so the bundle will crash at instantiation with \`ReferenceError: require is not defined\`, breaking EVERY workflow in this project.\nThis means a Node-only dependency (e.g. node:crypto, the Postgres driver, the OpenAI / AWS SDKs) is reachable from a workflow body or a "use step" that was not properly externalized. The most common cause is a workflow-steps helper that touches Node at module scope — it must reach Node only through a runtime-planted binding (see @stackbone/sdk/workflow-steps/*) or be wrapped in a "use step" that the WDK stubs out of the VM graph.\nFirst occurrence: line ${first.line}:${first.column}\n${listing}${more}\nFix: move the Node-bearing code behind a runtime binding, wrap the call in a "use step" function, or import the helper from @stackbone/sdk/workflow-steps/* (the per-domain entry that the WDK keeps out of the VM body).`);
31105
+ };
31106
+ /**
30833
31107
  * Build the workspace's workflows into `<projectRoot>/.well-known/workflow/v1/`:
30834
31108
  * - `step.mjs` — steps bundle (runtime external)
30835
31109
  * - `workflow.vm.js` — the workflow VM bundle text fed to `workflowEntrypoint`
@@ -30858,7 +31132,7 @@ var buildWorkflows = async (projectRoot, workflows, logger) => {
30858
31132
  stepsBundlePath: `./${WELL_KNOWN_DIR}/step.mjs`,
30859
31133
  workflowsBundlePath: `./${WELL_KNOWN_DIR}/workflow.vm.js`,
30860
31134
  webhookBundlePath: `./${WELL_KNOWN_DIR}/webhook.mjs`,
30861
- externalPackages: ["@stackbone/sdk", "@stackbone/sdk/*"]
31135
+ externalPackages: []
30862
31136
  });
30863
31137
  const prevCwd = process.cwd();
30864
31138
  let manifest;
@@ -30885,6 +31159,7 @@ var buildWorkflows = async (projectRoot, workflows, logger) => {
30885
31159
  const streaming = await detectStreamingWorkflows(projectRoot, workflows);
30886
31160
  foldSchemasIntoManifest(manifest, workflows, schemas, streaming, schedules);
30887
31161
  await writeFile(resolve(wkDir, "workflow.vm.js"), interimBundleText, "utf8");
31162
+ assertNoLeakedNodeRequire(interimBundleText);
30888
31163
  await writeFile(resolve(wkDir, "manifest.json"), JSON.stringify(manifest), "utf8");
30889
31164
  for (const name of [
30890
31165
  "step.mjs",
@@ -31179,7 +31454,8 @@ var loadWorkspace = async (projectRoot) => {
31179
31454
  if (!isWorkspace(workspace)) throw new Error(`${configPath} must default-export a workspace with \`agents\` and \`workflows\` arrays (via \`defineWorkspace({ agents, workflows })\`).`);
31180
31455
  return {
31181
31456
  agents: workspace.agents,
31182
- workflows: workspace.workflows
31457
+ workflows: workspace.workflows,
31458
+ deepAgents: workspace.deepAgents
31183
31459
  };
31184
31460
  };
31185
31461
  //#endregion
@@ -31249,10 +31525,11 @@ var discoverDeepAgents = async (root) => {
31249
31525
  /**
31250
31526
  * Derive `{ agents, workflows, deepAgents }` for the workspace rooted at `root`.
31251
31527
  * `agents` is always `[]` (there is no server-per-agent runtime). A `stackbone.config.ts`
31252
- * override wins for workflows; deep agents are always discovered by folder
31253
- * convention (the config format does not declare them), and `deepAgents` is
31254
- * omitted entirely when none are found so an empty workspace is byte-identical to
31255
- * before.
31528
+ * override wins for workflows AND deep agents; when the config declares `deepAgents`,
31529
+ * those win over auto-discovery (use to mark agents as `$internal` or customize the
31530
+ * registry). When omitted, deep agents are discovered by folder convention and
31531
+ * `deepAgents` is omitted entirely when none are found so an empty workspace is
31532
+ * byte-identical to before.
31256
31533
  */
31257
31534
  var discoverWorkspace = async (root) => {
31258
31535
  const deepAgents = await discoverDeepAgents(root);
@@ -31260,10 +31537,18 @@ var discoverWorkspace = async (root) => {
31260
31537
  ...ws,
31261
31538
  deepAgents
31262
31539
  };
31263
- if (await fileExists$1(resolve(root, "stackbone.config.ts"))) return withDeepAgents({
31264
- agents: [],
31265
- workflows: (await loadWorkspace(root)).workflows
31266
- });
31540
+ if (await fileExists$1(resolve(root, "stackbone.config.ts"))) {
31541
+ const ws = await loadWorkspace(root);
31542
+ const resultDeepAgents = ws.deepAgents ?? deepAgents;
31543
+ const baseResult = {
31544
+ agents: [],
31545
+ workflows: ws.workflows
31546
+ };
31547
+ return resultDeepAgents.length === 0 ? baseResult : {
31548
+ ...baseResult,
31549
+ deepAgents: resultDeepAgents
31550
+ };
31551
+ }
31267
31552
  return withDeepAgents({
31268
31553
  agents: [],
31269
31554
  workflows: await discoverWorkflows(root)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackbone/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
Binary file