vidpipe 1.3.12 → 1.3.14

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/dist/index.js CHANGED
@@ -93,7 +93,6 @@ var init_types = __esm({
93
93
  PipelineStage2["MediumClipPosts"] = "medium-clip-posts";
94
94
  PipelineStage2["Blog"] = "blog";
95
95
  PipelineStage2["QueueBuild"] = "queue-build";
96
- PipelineStage2["GitPush"] = "git-push";
97
96
  return PipelineStage2;
98
97
  })(PipelineStage || {});
99
98
  PIPELINE_STAGES = [
@@ -111,8 +110,7 @@ var init_types = __esm({
111
110
  { stage: "short-posts" /* ShortPosts */, name: "Short Posts", stageNumber: 12 },
112
111
  { stage: "medium-clip-posts" /* MediumClipPosts */, name: "Medium Clip Posts", stageNumber: 13 },
113
112
  { stage: "queue-build" /* QueueBuild */, name: "Queue Build", stageNumber: 14 },
114
- { stage: "blog" /* Blog */, name: "Blog", stageNumber: 15 },
115
- { stage: "git-push" /* GitPush */, name: "Git Push", stageNumber: 16 }
113
+ { stage: "blog" /* Blog */, name: "Blog", stageNumber: 15 }
116
114
  ];
117
115
  TOTAL_STAGES = PIPELINE_STAGES.length;
118
116
  PLATFORM_CHAR_LIMITS = {
@@ -143,7 +141,7 @@ function findRoot(startDir) {
143
141
  }
144
142
  }
145
143
  function projectRoot() {
146
- if (!_cachedRoot) _cachedRoot = findRoot(__dirname);
144
+ if (!_cachedRoot) _cachedRoot = findRoot(__dirname2);
147
145
  return _cachedRoot;
148
146
  }
149
147
  function assetsDir(...segments) {
@@ -157,11 +155,11 @@ function modelsDir() {
157
155
  const bundled = resolve2(projectRoot(), "dist", "models");
158
156
  return existsSync(bundled) ? bundled : assetsDir("models");
159
157
  }
160
- var __dirname, _cachedRoot;
158
+ var __dirname2, _cachedRoot;
161
159
  var init_paths = __esm({
162
160
  "src/L1-infra/paths/paths.ts"() {
163
161
  "use strict";
164
- __dirname = dirname2(fileURLToPath2(import.meta.url));
162
+ __dirname2 = dirname2(fileURLToPath2(import.meta.url));
165
163
  }
166
164
  });
167
165
 
@@ -448,6 +446,12 @@ function saveGlobalConfig(config2) {
448
446
  chmodSync(configPath, 384);
449
447
  }
450
448
  }
449
+ function getGlobalConfigValue(section, key) {
450
+ const config2 = loadGlobalConfig();
451
+ const sectionValues = config2[section];
452
+ const value = sectionValues[key];
453
+ return typeof value === "string" ? value : void 0;
454
+ }
451
455
  function setGlobalConfigValue(section, key, value) {
452
456
  const config2 = loadGlobalConfig();
453
457
  const sectionValues = config2[section];
@@ -471,8 +475,9 @@ var init_globalConfig = __esm({
471
475
 
472
476
  // src/L1-infra/config/configResolver.ts
473
477
  import { join as join4 } from "path";
474
- function resolveString(...sources) {
475
- for (const source of sources) {
478
+ function resolveString(cliValue, ...fallbacks) {
479
+ if (cliValue !== void 0) return cliValue;
480
+ for (const source of fallbacks) {
476
481
  if (source !== void 0 && source !== "") {
477
482
  return source;
478
483
  }
@@ -490,7 +495,11 @@ function resolveBoolean(cliValue, envValue, defaultValue) {
490
495
  }
491
496
  function resolveConfig(cliOptions = {}) {
492
497
  const globalConfig = loadGlobalConfig();
493
- const repoRoot = process.env.REPO_ROOT || process.cwd();
498
+ const repoRoot = resolveString(
499
+ cliOptions.repoRoot,
500
+ process.env.REPO_ROOT,
501
+ process.cwd()
502
+ );
494
503
  return {
495
504
  OPENAI_API_KEY: resolveString(
496
505
  cliOptions.openaiKey,
@@ -504,14 +513,22 @@ function resolveConfig(cliOptions = {}) {
504
513
  join4(repoRoot, "watch")
505
514
  ),
506
515
  REPO_ROOT: repoRoot,
507
- FFMPEG_PATH: resolveString(process.env.FFMPEG_PATH, "ffmpeg"),
508
- FFPROBE_PATH: resolveString(process.env.FFPROBE_PATH, "ffprobe"),
516
+ FFMPEG_PATH: resolveString(
517
+ cliOptions.ffmpegPath,
518
+ process.env.FFMPEG_PATH,
519
+ "ffmpeg"
520
+ ),
521
+ FFPROBE_PATH: resolveString(
522
+ cliOptions.ffprobePath,
523
+ process.env.FFPROBE_PATH,
524
+ "ffprobe"
525
+ ),
509
526
  EXA_API_KEY: resolveString(
510
527
  cliOptions.exaKey,
511
528
  process.env.EXA_API_KEY,
512
529
  globalConfig.credentials.exaApiKey
513
530
  ),
514
- EXA_MCP_URL: resolveString(process.env.EXA_MCP_URL, "https://mcp.exa.ai/mcp"),
531
+ EXA_MCP_URL: resolveString(void 0, process.env.EXA_MCP_URL, "https://mcp.exa.ai/mcp"),
515
532
  YOUTUBE_API_KEY: resolveString(
516
533
  cliOptions.youtubeKey,
517
534
  process.env.YOUTUBE_API_KEY,
@@ -523,12 +540,18 @@ function resolveConfig(cliOptions = {}) {
523
540
  globalConfig.credentials.perplexityApiKey
524
541
  ),
525
542
  LLM_PROVIDER: resolveString(
543
+ cliOptions.llmProvider,
526
544
  process.env.LLM_PROVIDER,
527
545
  globalConfig.defaults.llmProvider,
528
546
  "copilot"
529
547
  ),
530
- LLM_MODEL: resolveString(process.env.LLM_MODEL, globalConfig.defaults.llmModel),
548
+ LLM_MODEL: resolveString(
549
+ cliOptions.llmModel,
550
+ process.env.LLM_MODEL,
551
+ globalConfig.defaults.llmModel
552
+ ),
531
553
  ANTHROPIC_API_KEY: resolveString(
554
+ cliOptions.anthropicKey,
532
555
  process.env.ANTHROPIC_API_KEY,
533
556
  globalConfig.credentials.anthropicApiKey
534
557
  ),
@@ -545,7 +568,6 @@ function resolveConfig(cliOptions = {}) {
545
568
  join4(repoRoot, "brand.json")
546
569
  ),
547
570
  VERBOSE: cliOptions.verbose ?? false,
548
- SKIP_GIT: resolveBoolean(cliOptions.git === void 0 ? void 0 : !cliOptions.git, process.env.SKIP_GIT, false),
549
571
  SKIP_SILENCE_REMOVAL: resolveBoolean(
550
572
  cliOptions.silenceRemoval === void 0 ? void 0 : !cliOptions.silenceRemoval,
551
573
  process.env.SKIP_SILENCE_REMOVAL,
@@ -592,10 +614,12 @@ function resolveConfig(cliOptions = {}) {
592
614
  false
593
615
  ),
594
616
  GEMINI_API_KEY: resolveString(
617
+ cliOptions.geminiKey,
595
618
  process.env.GEMINI_API_KEY,
596
619
  globalConfig.credentials.geminiApiKey
597
620
  ),
598
621
  GEMINI_MODEL: resolveString(
622
+ cliOptions.geminiModel,
599
623
  process.env.GEMINI_MODEL,
600
624
  globalConfig.defaults.geminiModel,
601
625
  "gemini-2.5-pro"
@@ -610,9 +634,19 @@ function resolveConfig(cliOptions = {}) {
610
634
  cliOptions.githubToken,
611
635
  process.env.GITHUB_TOKEN,
612
636
  globalConfig.credentials.githubToken
613
- )
637
+ ),
638
+ MODEL_OVERRIDES: resolveModelOverrides()
614
639
  };
615
640
  }
641
+ function resolveModelOverrides() {
642
+ const overrides = {};
643
+ for (const [key, value] of Object.entries(process.env)) {
644
+ if (key.startsWith("MODEL_") && value) {
645
+ overrides[key] = value;
646
+ }
647
+ }
648
+ return overrides;
649
+ }
616
650
  var init_configResolver = __esm({
617
651
  "src/L1-infra/config/configResolver.ts"() {
618
652
  "use strict";
@@ -628,7 +662,7 @@ __export(environment_exports, {
628
662
  validateRequiredKeys: () => validateRequiredKeys
629
663
  });
630
664
  function validateRequiredKeys() {
631
- if (!config?.OPENAI_API_KEY && !process.env.OPENAI_API_KEY) {
665
+ if (!config?.OPENAI_API_KEY) {
632
666
  throw new Error("Missing required: OPENAI_API_KEY (set via --openai-key, env var, or vidpipe configure)");
633
667
  }
634
668
  }
@@ -1439,7 +1473,7 @@ var require_messages = __commonJS({
1439
1473
  ErrorCodes2.jsonrpcReservedErrorRangeEnd = -32e3;
1440
1474
  ErrorCodes2.serverErrorEnd = -32e3;
1441
1475
  })(ErrorCodes || (exports.ErrorCodes = ErrorCodes = {}));
1442
- var ResponseError = class _ResponseError extends Error {
1476
+ var ResponseError2 = class _ResponseError extends Error {
1443
1477
  constructor(code, message, data) {
1444
1478
  super(message);
1445
1479
  this.code = is.number(code) ? code : ErrorCodes.UnknownErrorCode;
@@ -1457,7 +1491,7 @@ var require_messages = __commonJS({
1457
1491
  return result;
1458
1492
  }
1459
1493
  };
1460
- exports.ResponseError = ResponseError;
1494
+ exports.ResponseError = ResponseError2;
1461
1495
  var ParameterStructures = class _ParameterStructures {
1462
1496
  constructor(kind) {
1463
1497
  this.kind = kind;
@@ -3010,14 +3044,14 @@ var require_connection = __commonJS({
3010
3044
  ConnectionErrors2[ConnectionErrors2["Disposed"] = 2] = "Disposed";
3011
3045
  ConnectionErrors2[ConnectionErrors2["AlreadyListening"] = 3] = "AlreadyListening";
3012
3046
  })(ConnectionErrors || (exports.ConnectionErrors = ConnectionErrors = {}));
3013
- var ConnectionError = class _ConnectionError extends Error {
3047
+ var ConnectionError2 = class _ConnectionError extends Error {
3014
3048
  constructor(code, message) {
3015
3049
  super(message);
3016
3050
  this.code = code;
3017
3051
  Object.setPrototypeOf(this, _ConnectionError.prototype);
3018
3052
  }
3019
3053
  };
3020
- exports.ConnectionError = ConnectionError;
3054
+ exports.ConnectionError = ConnectionError2;
3021
3055
  var ConnectionStrategy;
3022
3056
  (function(ConnectionStrategy2) {
3023
3057
  function is(value) {
@@ -3640,15 +3674,15 @@ ${JSON.stringify(message, null, 4)}`);
3640
3674
  }
3641
3675
  function throwIfClosedOrDisposed() {
3642
3676
  if (isClosed()) {
3643
- throw new ConnectionError(ConnectionErrors.Closed, "Connection is closed.");
3677
+ throw new ConnectionError2(ConnectionErrors.Closed, "Connection is closed.");
3644
3678
  }
3645
3679
  if (isDisposed()) {
3646
- throw new ConnectionError(ConnectionErrors.Disposed, "Connection is disposed.");
3680
+ throw new ConnectionError2(ConnectionErrors.Disposed, "Connection is disposed.");
3647
3681
  }
3648
3682
  }
3649
3683
  function throwIfListening() {
3650
3684
  if (isListening()) {
3651
- throw new ConnectionError(ConnectionErrors.AlreadyListening, "Connection is already listening");
3685
+ throw new ConnectionError2(ConnectionErrors.AlreadyListening, "Connection is already listening");
3652
3686
  }
3653
3687
  }
3654
3688
  function throwIfNotListening() {
@@ -4633,6 +4667,150 @@ var require_node = __commonJS({
4633
4667
  }
4634
4668
  });
4635
4669
 
4670
+ // src/L3-services/postStore/postStore.ts
4671
+ function getQueueDir() {
4672
+ const { OUTPUT_DIR } = getConfig();
4673
+ return join(OUTPUT_DIR, "publish-queue");
4674
+ }
4675
+ function getPublishedDir() {
4676
+ const { OUTPUT_DIR } = getConfig();
4677
+ return join(OUTPUT_DIR, "published");
4678
+ }
4679
+ async function readQueueItem(folderPath, id) {
4680
+ const metadataPath = join(folderPath, "metadata.json");
4681
+ const postPath = join(folderPath, "post.md");
4682
+ try {
4683
+ const metadataRaw = await readTextFile(metadataPath);
4684
+ const metadata = JSON.parse(metadataRaw);
4685
+ let postContent = "";
4686
+ try {
4687
+ postContent = await readTextFile(postPath);
4688
+ } catch {
4689
+ logger_default.debug(`No post.md found for ${String(id).replace(/[\r\n]/g, "")}`);
4690
+ }
4691
+ const videoPath = join(folderPath, "media.mp4");
4692
+ const imagePath = join(folderPath, "media.png");
4693
+ let mediaPath = null;
4694
+ let hasMedia = false;
4695
+ if (await fileExists(videoPath)) {
4696
+ mediaPath = videoPath;
4697
+ hasMedia = true;
4698
+ } else if (await fileExists(imagePath)) {
4699
+ mediaPath = imagePath;
4700
+ hasMedia = true;
4701
+ }
4702
+ return {
4703
+ id,
4704
+ metadata,
4705
+ postContent,
4706
+ hasMedia,
4707
+ mediaPath,
4708
+ folderPath
4709
+ };
4710
+ } catch (err) {
4711
+ logger_default.debug(`Failed to read queue item ${String(id).replace(/[\r\n]/g, "")}: ${String(err).replace(/[\r\n]/g, "")}`);
4712
+ return null;
4713
+ }
4714
+ }
4715
+ async function getPendingItems() {
4716
+ const queueDir = getQueueDir();
4717
+ await ensureDirectory(queueDir);
4718
+ let entries;
4719
+ try {
4720
+ const dirents = await listDirectoryWithTypes(queueDir);
4721
+ entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
4722
+ } catch {
4723
+ return [];
4724
+ }
4725
+ const items = [];
4726
+ for (const name of entries) {
4727
+ const item = await readQueueItem(join(queueDir, name), name);
4728
+ if (item) items.push(item);
4729
+ }
4730
+ items.sort((a, b) => {
4731
+ if (a.hasMedia !== b.hasMedia) return a.hasMedia ? -1 : 1;
4732
+ return a.metadata.createdAt.localeCompare(b.metadata.createdAt);
4733
+ });
4734
+ return items;
4735
+ }
4736
+ async function createItem(id, metadata, postContent, mediaSourcePath) {
4737
+ if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
4738
+ throw new Error(`Invalid ID format: ${id}`);
4739
+ }
4740
+ const folderPath = join(getQueueDir(), basename(id));
4741
+ await ensureDirectory(folderPath);
4742
+ await writeJsonFile(join(folderPath, "metadata.json"), metadata);
4743
+ await writeTextFile(join(folderPath, "post.md"), postContent);
4744
+ let hasMedia = false;
4745
+ const ext = mediaSourcePath ? extname(mediaSourcePath) : ".mp4";
4746
+ const mediaFilename = `media${ext}`;
4747
+ const mediaPath = join(folderPath, mediaFilename);
4748
+ if (mediaSourcePath) {
4749
+ await copyFile(mediaSourcePath, mediaPath);
4750
+ hasMedia = true;
4751
+ }
4752
+ logger_default.debug(`Created queue item: ${String(id).replace(/[\r\n]/g, "")}`);
4753
+ return {
4754
+ id,
4755
+ metadata,
4756
+ postContent,
4757
+ hasMedia,
4758
+ mediaPath: hasMedia ? mediaPath : null,
4759
+ folderPath
4760
+ };
4761
+ }
4762
+ async function getPublishedItems() {
4763
+ const publishedDir = getPublishedDir();
4764
+ await ensureDirectory(publishedDir);
4765
+ let entries;
4766
+ try {
4767
+ const dirents = await listDirectoryWithTypes(publishedDir);
4768
+ entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
4769
+ } catch {
4770
+ return [];
4771
+ }
4772
+ const items = [];
4773
+ for (const name of entries) {
4774
+ const item = await readQueueItem(join(publishedDir, name), name);
4775
+ if (item) items.push(item);
4776
+ }
4777
+ items.sort((a, b) => a.metadata.createdAt.localeCompare(b.metadata.createdAt));
4778
+ return items;
4779
+ }
4780
+ async function getScheduledItemsByIdeaIds(ideaIds) {
4781
+ if (ideaIds.length === 0) return [];
4782
+ const ideaIdSet = new Set(ideaIds);
4783
+ const [pendingItems, publishedItems] = await Promise.all([
4784
+ getPendingItems(),
4785
+ getPublishedItems()
4786
+ ]);
4787
+ return [...pendingItems, ...publishedItems].filter(
4788
+ (item) => item.metadata.ideaIds?.some((id) => ideaIdSet.has(id)) ?? false
4789
+ );
4790
+ }
4791
+ async function itemExists(id) {
4792
+ if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
4793
+ throw new Error(`Invalid ID format: ${id}`);
4794
+ }
4795
+ if (await fileExists(join(getQueueDir(), basename(id)))) {
4796
+ return "pending";
4797
+ }
4798
+ if (await fileExists(join(getPublishedDir(), basename(id)))) {
4799
+ return "published";
4800
+ }
4801
+ return null;
4802
+ }
4803
+ var init_postStore = __esm({
4804
+ "src/L3-services/postStore/postStore.ts"() {
4805
+ "use strict";
4806
+ init_types();
4807
+ init_environment();
4808
+ init_configLogger();
4809
+ init_fileSystem();
4810
+ init_paths();
4811
+ }
4812
+ });
4813
+
4636
4814
  // src/L7-app/sdk/VidPipeSDK.ts
4637
4815
  init_types();
4638
4816
  init_environment();
@@ -4640,6 +4818,47 @@ init_globalConfig();
4640
4818
  init_fileSystem();
4641
4819
  init_paths();
4642
4820
 
4821
+ // src/L1-infra/progress/progressEmitter.ts
4822
+ var ProgressEmitter = class {
4823
+ enabled = false;
4824
+ listeners = /* @__PURE__ */ new Set();
4825
+ /** Turn on progress event output to stderr. */
4826
+ enable() {
4827
+ this.enabled = true;
4828
+ }
4829
+ /** Turn off progress event output. */
4830
+ disable() {
4831
+ this.enabled = false;
4832
+ }
4833
+ /** Whether the emitter is currently active (stderr or listeners). */
4834
+ isEnabled() {
4835
+ return this.enabled || this.listeners.size > 0;
4836
+ }
4837
+ /** Register a programmatic listener for progress events. */
4838
+ addListener(fn) {
4839
+ this.listeners.add(fn);
4840
+ }
4841
+ /** Remove a previously registered listener. */
4842
+ removeListener(fn) {
4843
+ this.listeners.delete(fn);
4844
+ }
4845
+ /**
4846
+ * Write a progress event as a single JSON line to stderr (if enabled)
4847
+ * and dispatch to all registered listeners.
4848
+ * No-op when neither stderr output nor listeners are active.
4849
+ */
4850
+ emit(event) {
4851
+ if (!this.enabled && this.listeners.size === 0) return;
4852
+ if (this.enabled) {
4853
+ process.stderr.write(JSON.stringify(event) + "\n");
4854
+ }
4855
+ for (const listener of this.listeners) {
4856
+ listener(event);
4857
+ }
4858
+ }
4859
+ };
4860
+ var progressEmitter = new ProgressEmitter();
4861
+
4643
4862
  // src/L1-infra/process/process.ts
4644
4863
  import { execFile as nodeExecFile, execSync as nodeExecSync, spawnSync as nodeSpawnSync } from "child_process";
4645
4864
  import { createRequire } from "module";
@@ -4648,9 +4867,6 @@ function execFileRaw(cmd, args, opts, callback) {
4648
4867
  callback(error, String(stdout ?? ""), String(stderr ?? ""));
4649
4868
  });
4650
4869
  }
4651
- function execCommandSync(cmd, opts) {
4652
- return nodeExecSync(cmd, { encoding: "utf-8", ...opts }).toString().trim();
4653
- }
4654
4870
  function spawnCommand(cmd, args, opts) {
4655
4871
  return nodeSpawnSync(cmd, args, { encoding: "utf-8", ...opts });
4656
4872
  }
@@ -4744,12 +4960,13 @@ var AGENT_MODEL_MAP = {
4744
4960
  ProducerAgent: PREMIUM_MODEL
4745
4961
  };
4746
4962
  function getModelForAgent(agentName) {
4963
+ const config2 = getConfig();
4747
4964
  const envKey = `MODEL_${agentName.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()}`;
4748
- const envOverride = process.env[envKey];
4965
+ const envOverride = config2.MODEL_OVERRIDES[envKey];
4749
4966
  if (envOverride) return envOverride;
4750
4967
  const mapped = AGENT_MODEL_MAP[agentName];
4751
4968
  if (mapped) return mapped;
4752
- const global = getConfig().LLM_MODEL;
4969
+ const global = config2.LLM_MODEL;
4753
4970
  if (global) return global;
4754
4971
  return void 0;
4755
4972
  }
@@ -4765,20 +4982,77 @@ import { default as default4 } from "openai";
4765
4982
  import { default as default5 } from "@anthropic-ai/sdk";
4766
4983
 
4767
4984
  // node_modules/@github/copilot-sdk/dist/client.js
4768
- var import_node = __toESM(require_node(), 1);
4985
+ var import_node2 = __toESM(require_node(), 1);
4769
4986
  import { spawn } from "child_process";
4770
4987
  import { existsSync as existsSync4 } from "fs";
4771
4988
  import { Socket } from "net";
4772
4989
  import { dirname as dirname3, join as join5 } from "path";
4773
4990
  import { fileURLToPath as fileURLToPath3 } from "url";
4774
4991
 
4992
+ // node_modules/@github/copilot-sdk/dist/generated/rpc.js
4993
+ function createServerRpc(connection) {
4994
+ return {
4995
+ ping: async (params) => connection.sendRequest("ping", params),
4996
+ models: {
4997
+ list: async () => connection.sendRequest("models.list", {})
4998
+ },
4999
+ tools: {
5000
+ list: async (params) => connection.sendRequest("tools.list", params)
5001
+ },
5002
+ account: {
5003
+ getQuota: async () => connection.sendRequest("account.getQuota", {})
5004
+ }
5005
+ };
5006
+ }
5007
+ function createSessionRpc(connection, sessionId) {
5008
+ return {
5009
+ model: {
5010
+ getCurrent: async () => connection.sendRequest("session.model.getCurrent", { sessionId }),
5011
+ switchTo: async (params) => connection.sendRequest("session.model.switchTo", { sessionId, ...params })
5012
+ },
5013
+ mode: {
5014
+ get: async () => connection.sendRequest("session.mode.get", { sessionId }),
5015
+ set: async (params) => connection.sendRequest("session.mode.set", { sessionId, ...params })
5016
+ },
5017
+ plan: {
5018
+ read: async () => connection.sendRequest("session.plan.read", { sessionId }),
5019
+ update: async (params) => connection.sendRequest("session.plan.update", { sessionId, ...params }),
5020
+ delete: async () => connection.sendRequest("session.plan.delete", { sessionId })
5021
+ },
5022
+ workspace: {
5023
+ listFiles: async () => connection.sendRequest("session.workspace.listFiles", { sessionId }),
5024
+ readFile: async (params) => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }),
5025
+ createFile: async (params) => connection.sendRequest("session.workspace.createFile", { sessionId, ...params })
5026
+ },
5027
+ fleet: {
5028
+ start: async (params) => connection.sendRequest("session.fleet.start", { sessionId, ...params })
5029
+ },
5030
+ agent: {
5031
+ list: async () => connection.sendRequest("session.agent.list", { sessionId }),
5032
+ getCurrent: async () => connection.sendRequest("session.agent.getCurrent", { sessionId }),
5033
+ select: async (params) => connection.sendRequest("session.agent.select", { sessionId, ...params }),
5034
+ deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId })
5035
+ },
5036
+ compaction: {
5037
+ compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
5038
+ },
5039
+ tools: {
5040
+ handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
5041
+ },
5042
+ permissions: {
5043
+ handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
5044
+ }
5045
+ };
5046
+ }
5047
+
4775
5048
  // node_modules/@github/copilot-sdk/dist/sdkProtocolVersion.js
4776
- var SDK_PROTOCOL_VERSION = 2;
5049
+ var SDK_PROTOCOL_VERSION = 3;
4777
5050
  function getSdkProtocolVersion() {
4778
5051
  return SDK_PROTOCOL_VERSION;
4779
5052
  }
4780
5053
 
4781
5054
  // node_modules/@github/copilot-sdk/dist/session.js
5055
+ var import_node = __toESM(require_node(), 1);
4782
5056
  var CopilotSession = class {
4783
5057
  /**
4784
5058
  * Creates a new CopilotSession instance.
@@ -4799,6 +5073,16 @@ var CopilotSession = class {
4799
5073
  permissionHandler;
4800
5074
  userInputHandler;
4801
5075
  hooks;
5076
+ _rpc = null;
5077
+ /**
5078
+ * Typed session-scoped RPC methods.
5079
+ */
5080
+ get rpc() {
5081
+ if (!this._rpc) {
5082
+ this._rpc = createSessionRpc(this.connection, this.sessionId);
5083
+ }
5084
+ return this._rpc;
5085
+ }
4802
5086
  /**
4803
5087
  * Path to the session workspace directory when infinite sessions are enabled.
4804
5088
  * Contains checkpoints/, plan.md, and files/ subdirectories.
@@ -4815,7 +5099,7 @@ var CopilotSession = class {
4815
5099
  *
4816
5100
  * @param options - The message options including the prompt and optional attachments
4817
5101
  * @returns A promise that resolves with the message ID of the response
4818
- * @throws Error if the session has been destroyed or the connection fails
5102
+ * @throws Error if the session has been disconnected or the connection fails
4819
5103
  *
4820
5104
  * @example
4821
5105
  * ```typescript
@@ -4848,7 +5132,7 @@ var CopilotSession = class {
4848
5132
  * @returns A promise that resolves with the final assistant message when the session becomes idle,
4849
5133
  * or undefined if no assistant message was received
4850
5134
  * @throws Error if the timeout is reached before the session becomes idle
4851
- * @throws Error if the session has been destroyed or the connection fails
5135
+ * @throws Error if the session has been disconnected or the connection fails
4852
5136
  *
4853
5137
  * @example
4854
5138
  * ```typescript
@@ -4922,11 +5206,13 @@ var CopilotSession = class {
4922
5206
  }
4923
5207
  /**
4924
5208
  * Dispatches an event to all registered handlers.
5209
+ * Also handles broadcast request events internally (external tool calls, permissions).
4925
5210
  *
4926
5211
  * @param event - The session event to dispatch
4927
5212
  * @internal This method is for internal use by the SDK.
4928
5213
  */
4929
5214
  _dispatchEvent(event) {
5215
+ this._handleBroadcastEvent(event);
4930
5216
  const typedHandlers = this.typedEventHandlers.get(event.type);
4931
5217
  if (typedHandlers) {
4932
5218
  for (const handler of typedHandlers) {
@@ -4943,6 +5229,85 @@ var CopilotSession = class {
4943
5229
  }
4944
5230
  }
4945
5231
  }
5232
+ /**
5233
+ * Handles broadcast request events by executing local handlers and responding via RPC.
5234
+ * Handlers are dispatched as fire-and-forget — rejections propagate as unhandled promise
5235
+ * rejections, consistent with standard EventEmitter / event handler semantics.
5236
+ * @internal
5237
+ */
5238
+ _handleBroadcastEvent(event) {
5239
+ if (event.type === "external_tool.requested") {
5240
+ const { requestId, toolName } = event.data;
5241
+ const args = event.data.arguments;
5242
+ const toolCallId = event.data.toolCallId;
5243
+ const handler = this.toolHandlers.get(toolName);
5244
+ if (handler) {
5245
+ void this._executeToolAndRespond(requestId, toolName, toolCallId, args, handler);
5246
+ }
5247
+ } else if (event.type === "permission.requested") {
5248
+ const { requestId, permissionRequest } = event.data;
5249
+ if (this.permissionHandler) {
5250
+ void this._executePermissionAndRespond(requestId, permissionRequest);
5251
+ }
5252
+ }
5253
+ }
5254
+ /**
5255
+ * Executes a tool handler and sends the result back via RPC.
5256
+ * @internal
5257
+ */
5258
+ async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler) {
5259
+ try {
5260
+ const rawResult = await handler(args, {
5261
+ sessionId: this.sessionId,
5262
+ toolCallId,
5263
+ toolName,
5264
+ arguments: args
5265
+ });
5266
+ let result;
5267
+ if (rawResult == null) {
5268
+ result = "";
5269
+ } else if (typeof rawResult === "string") {
5270
+ result = rawResult;
5271
+ } else {
5272
+ result = JSON.stringify(rawResult);
5273
+ }
5274
+ await this.rpc.tools.handlePendingToolCall({ requestId, result });
5275
+ } catch (error) {
5276
+ const message = error instanceof Error ? error.message : String(error);
5277
+ try {
5278
+ await this.rpc.tools.handlePendingToolCall({ requestId, error: message });
5279
+ } catch (rpcError) {
5280
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
5281
+ throw rpcError;
5282
+ }
5283
+ }
5284
+ }
5285
+ }
5286
+ /**
5287
+ * Executes a permission handler and sends the result back via RPC.
5288
+ * @internal
5289
+ */
5290
+ async _executePermissionAndRespond(requestId, permissionRequest) {
5291
+ try {
5292
+ const result = await this.permissionHandler(permissionRequest, {
5293
+ sessionId: this.sessionId
5294
+ });
5295
+ await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
5296
+ } catch (_error) {
5297
+ try {
5298
+ await this.rpc.permissions.handlePendingPermissionRequest({
5299
+ requestId,
5300
+ result: {
5301
+ kind: "denied-no-approval-rule-and-could-not-request-from-user"
5302
+ }
5303
+ });
5304
+ } catch (rpcError) {
5305
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
5306
+ throw rpcError;
5307
+ }
5308
+ }
5309
+ }
5310
+ }
4946
5311
  /**
4947
5312
  * Registers custom tool handlers for this session.
4948
5313
  *
@@ -5008,13 +5373,14 @@ var CopilotSession = class {
5008
5373
  this.hooks = hooks;
5009
5374
  }
5010
5375
  /**
5011
- * Handles a permission request from the Copilot CLI.
5376
+ * Handles a permission request in the v2 protocol format (synchronous RPC).
5377
+ * Used as a back-compat adapter when connected to a v2 server.
5012
5378
  *
5013
5379
  * @param request - The permission request data from the CLI
5014
5380
  * @returns A promise that resolves with the permission decision
5015
5381
  * @internal This method is for internal use by the SDK.
5016
5382
  */
5017
- async _handlePermissionRequest(request) {
5383
+ async _handlePermissionRequestV2(request) {
5018
5384
  if (!this.permissionHandler) {
5019
5385
  return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
5020
5386
  }
@@ -5085,7 +5451,7 @@ var CopilotSession = class {
5085
5451
  * assistant responses, tool executions, and other session events.
5086
5452
  *
5087
5453
  * @returns A promise that resolves with an array of all session events
5088
- * @throws Error if the session has been destroyed or the connection fails
5454
+ * @throws Error if the session has been disconnected or the connection fails
5089
5455
  *
5090
5456
  * @example
5091
5457
  * ```typescript
@@ -5104,22 +5470,27 @@ var CopilotSession = class {
5104
5470
  return response.events;
5105
5471
  }
5106
5472
  /**
5107
- * Destroys this session and releases all associated resources.
5473
+ * Disconnects this session and releases all in-memory resources (event handlers,
5474
+ * tool handlers, permission handlers).
5108
5475
  *
5109
- * After calling this method, the session can no longer be used. All event
5110
- * handlers and tool handlers are cleared. To continue the conversation,
5111
- * use {@link CopilotClient.resumeSession} with the session ID.
5476
+ * Session state on disk (conversation history, planning state, artifacts) is
5477
+ * preserved, so the conversation can be resumed later by calling
5478
+ * {@link CopilotClient.resumeSession} with the session ID. To permanently
5479
+ * remove all session data including files on disk, use
5480
+ * {@link CopilotClient.deleteSession} instead.
5112
5481
  *
5113
- * @returns A promise that resolves when the session is destroyed
5482
+ * After calling this method, the session object can no longer be used.
5483
+ *
5484
+ * @returns A promise that resolves when the session is disconnected
5114
5485
  * @throws Error if the connection fails
5115
5486
  *
5116
5487
  * @example
5117
5488
  * ```typescript
5118
- * // Clean up when done
5119
- * await session.destroy();
5489
+ * // Clean up when done — session can still be resumed later
5490
+ * await session.disconnect();
5120
5491
  * ```
5121
5492
  */
5122
- async destroy() {
5493
+ async disconnect() {
5123
5494
  await this.connection.sendRequest("session.destroy", {
5124
5495
  sessionId: this.sessionId
5125
5496
  });
@@ -5128,6 +5499,22 @@ var CopilotSession = class {
5128
5499
  this.toolHandlers.clear();
5129
5500
  this.permissionHandler = void 0;
5130
5501
  }
5502
+ /**
5503
+ * @deprecated Use {@link disconnect} instead. This method will be removed in a future release.
5504
+ *
5505
+ * Disconnects this session and releases all in-memory resources.
5506
+ * Session data on disk is preserved for later resumption.
5507
+ *
5508
+ * @returns A promise that resolves when the session is disconnected
5509
+ * @throws Error if the connection fails
5510
+ */
5511
+ async destroy() {
5512
+ return this.disconnect();
5513
+ }
5514
+ /** Enables `await using session = ...` syntax for automatic cleanup. */
5515
+ async [Symbol.asyncDispose]() {
5516
+ return this.disconnect();
5517
+ }
5131
5518
  /**
5132
5519
  * Aborts the currently processing message in this session.
5133
5520
  *
@@ -5135,7 +5522,7 @@ var CopilotSession = class {
5135
5522
  * and can continue to be used for new messages.
5136
5523
  *
5137
5524
  * @returns A promise that resolves when the abort request is acknowledged
5138
- * @throws Error if the session has been destroyed or the connection fails
5525
+ * @throws Error if the session has been disconnected or the connection fails
5139
5526
  *
5140
5527
  * @example
5141
5528
  * ```typescript
@@ -5153,12 +5540,27 @@ var CopilotSession = class {
5153
5540
  sessionId: this.sessionId
5154
5541
  });
5155
5542
  }
5156
- };
5157
-
5158
- // node_modules/@github/copilot-sdk/dist/client.js
5159
- function isZodSchema(value) {
5160
- return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
5161
- }
5543
+ /**
5544
+ * Change the model for this session.
5545
+ * The new model takes effect for the next message. Conversation history is preserved.
5546
+ *
5547
+ * @param model - Model ID to switch to
5548
+ *
5549
+ * @example
5550
+ * ```typescript
5551
+ * await session.setModel("gpt-4.1");
5552
+ * ```
5553
+ */
5554
+ async setModel(model) {
5555
+ await this.rpc.model.switchTo({ modelId: model });
5556
+ }
5557
+ };
5558
+
5559
+ // node_modules/@github/copilot-sdk/dist/client.js
5560
+ var MIN_PROTOCOL_VERSION = 2;
5561
+ function isZodSchema(value) {
5562
+ return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
5563
+ }
5162
5564
  function toJsonSchema(parameters) {
5163
5565
  if (!parameters) return void 0;
5164
5566
  if (isZodSchema(parameters)) {
@@ -5166,6 +5568,12 @@ function toJsonSchema(parameters) {
5166
5568
  }
5167
5569
  return parameters;
5168
5570
  }
5571
+ function getNodeExecPath() {
5572
+ if (process.versions.bun) {
5573
+ return "node";
5574
+ }
5575
+ return process.execPath;
5576
+ }
5169
5577
  function getBundledCliPath() {
5170
5578
  const sdkUrl = import.meta.resolve("@github/copilot/sdk");
5171
5579
  const sdkPath = fileURLToPath3(sdkUrl);
@@ -5179,6 +5587,8 @@ var CopilotClient = class {
5179
5587
  actualHost = "localhost";
5180
5588
  state = "disconnected";
5181
5589
  sessions = /* @__PURE__ */ new Map();
5590
+ stderrBuffer = "";
5591
+ // Captures CLI stderr for error messages
5182
5592
  options;
5183
5593
  isExternalServer = false;
5184
5594
  forceStopping = false;
@@ -5186,6 +5596,23 @@ var CopilotClient = class {
5186
5596
  modelsCacheLock = Promise.resolve();
5187
5597
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
5188
5598
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
5599
+ _rpc = null;
5600
+ processExitPromise = null;
5601
+ // Rejects when CLI process exits
5602
+ negotiatedProtocolVersion = null;
5603
+ /**
5604
+ * Typed server-scoped RPC methods.
5605
+ * @throws Error if the client is not connected
5606
+ */
5607
+ get rpc() {
5608
+ if (!this.connection) {
5609
+ throw new Error("Client is not connected. Call start() first.");
5610
+ }
5611
+ if (!this._rpc) {
5612
+ this._rpc = createServerRpc(this.connection);
5613
+ }
5614
+ return this._rpc;
5615
+ }
5189
5616
  /**
5190
5617
  * Creates a new CopilotClient instance.
5191
5618
  *
@@ -5211,6 +5638,11 @@ var CopilotClient = class {
5211
5638
  if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
5212
5639
  throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
5213
5640
  }
5641
+ if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
5642
+ throw new Error(
5643
+ "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
5644
+ );
5645
+ }
5214
5646
  if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== void 0)) {
5215
5647
  throw new Error(
5216
5648
  "githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
@@ -5222,6 +5654,9 @@ var CopilotClient = class {
5222
5654
  this.actualPort = port;
5223
5655
  this.isExternalServer = true;
5224
5656
  }
5657
+ if (options.isChildProcess) {
5658
+ this.isExternalServer = true;
5659
+ }
5225
5660
  this.options = {
5226
5661
  cliPath: options.cliPath || getBundledCliPath(),
5227
5662
  cliArgs: options.cliArgs ?? [],
@@ -5229,6 +5664,7 @@ var CopilotClient = class {
5229
5664
  port: options.port || 0,
5230
5665
  useStdio: options.cliUrl ? false : options.useStdio ?? true,
5231
5666
  // Default to stdio unless cliUrl is provided
5667
+ isChildProcess: options.isChildProcess ?? false,
5232
5668
  cliUrl: options.cliUrl,
5233
5669
  logLevel: options.logLevel || "debug",
5234
5670
  autoStart: options.autoStart ?? true,
@@ -5300,10 +5736,14 @@ var CopilotClient = class {
5300
5736
  * Stops the CLI server and closes all active sessions.
5301
5737
  *
5302
5738
  * This method performs graceful cleanup:
5303
- * 1. Destroys all active sessions with retry logic
5739
+ * 1. Closes all active sessions (releases in-memory resources)
5304
5740
  * 2. Closes the JSON-RPC connection
5305
5741
  * 3. Terminates the CLI server process (if spawned by this client)
5306
5742
  *
5743
+ * Note: session data on disk is preserved, so sessions can be resumed later.
5744
+ * To permanently remove session data before stopping, call
5745
+ * {@link deleteSession} for each session first.
5746
+ *
5307
5747
  * @returns A promise that resolves with an array of errors encountered during cleanup.
5308
5748
  * An empty array indicates all cleanup succeeded.
5309
5749
  *
@@ -5322,7 +5762,7 @@ var CopilotClient = class {
5322
5762
  let lastError = null;
5323
5763
  for (let attempt = 1; attempt <= 3; attempt++) {
5324
5764
  try {
5325
- await session.destroy();
5765
+ await session.disconnect();
5326
5766
  lastError = null;
5327
5767
  break;
5328
5768
  } catch (error) {
@@ -5336,7 +5776,7 @@ var CopilotClient = class {
5336
5776
  if (lastError) {
5337
5777
  errors.push(
5338
5778
  new Error(
5339
- `Failed to destroy session ${sessionId} after 3 attempts: ${lastError.message}`
5779
+ `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`
5340
5780
  )
5341
5781
  );
5342
5782
  }
@@ -5353,6 +5793,7 @@ var CopilotClient = class {
5353
5793
  );
5354
5794
  }
5355
5795
  this.connection = null;
5796
+ this._rpc = null;
5356
5797
  }
5357
5798
  this.modelsCache = null;
5358
5799
  if (this.socket) {
@@ -5381,6 +5822,8 @@ var CopilotClient = class {
5381
5822
  }
5382
5823
  this.state = "disconnected";
5383
5824
  this.actualPort = null;
5825
+ this.stderrBuffer = "";
5826
+ this.processExitPromise = null;
5384
5827
  return errors;
5385
5828
  }
5386
5829
  /**
@@ -5417,6 +5860,7 @@ var CopilotClient = class {
5417
5860
  } catch {
5418
5861
  }
5419
5862
  this.connection = null;
5863
+ this._rpc = null;
5420
5864
  }
5421
5865
  this.modelsCache = null;
5422
5866
  if (this.socket) {
@@ -5435,6 +5879,8 @@ var CopilotClient = class {
5435
5879
  }
5436
5880
  this.state = "disconnected";
5437
5881
  this.actualPort = null;
5882
+ this.stderrBuffer = "";
5883
+ this.processExitPromise = null;
5438
5884
  }
5439
5885
  /**
5440
5886
  * Creates a new conversation session with the Copilot CLI.
@@ -5450,10 +5896,11 @@ var CopilotClient = class {
5450
5896
  * @example
5451
5897
  * ```typescript
5452
5898
  * // Basic session
5453
- * const session = await client.createSession();
5899
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
5454
5900
  *
5455
5901
  * // Session with model and tools
5456
5902
  * const session = await client.createSession({
5903
+ * onPermissionRequest: approveAll,
5457
5904
  * model: "gpt-4",
5458
5905
  * tools: [{
5459
5906
  * name: "get_weather",
@@ -5464,7 +5911,12 @@ var CopilotClient = class {
5464
5911
  * });
5465
5912
  * ```
5466
5913
  */
5467
- async createSession(config2 = {}) {
5914
+ async createSession(config2) {
5915
+ if (!config2?.onPermissionRequest) {
5916
+ throw new Error(
5917
+ "An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
5918
+ );
5919
+ }
5468
5920
  if (!this.connection) {
5469
5921
  if (this.options.autoStart) {
5470
5922
  await this.start();
@@ -5475,22 +5927,25 @@ var CopilotClient = class {
5475
5927
  const response = await this.connection.sendRequest("session.create", {
5476
5928
  model: config2.model,
5477
5929
  sessionId: config2.sessionId,
5930
+ clientName: config2.clientName,
5478
5931
  reasoningEffort: config2.reasoningEffort,
5479
5932
  tools: config2.tools?.map((tool) => ({
5480
5933
  name: tool.name,
5481
5934
  description: tool.description,
5482
- parameters: toJsonSchema(tool.parameters)
5935
+ parameters: toJsonSchema(tool.parameters),
5936
+ overridesBuiltInTool: tool.overridesBuiltInTool
5483
5937
  })),
5484
5938
  systemMessage: config2.systemMessage,
5485
5939
  availableTools: config2.availableTools,
5486
5940
  excludedTools: config2.excludedTools,
5487
5941
  provider: config2.provider,
5488
- requestPermission: !!config2.onPermissionRequest,
5942
+ requestPermission: true,
5489
5943
  requestUserInput: !!config2.onUserInputRequest,
5490
5944
  hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
5491
5945
  workingDirectory: config2.workingDirectory,
5492
5946
  streaming: config2.streaming,
5493
5947
  mcpServers: config2.mcpServers,
5948
+ envValueMode: "direct",
5494
5949
  customAgents: config2.customAgents,
5495
5950
  configDir: config2.configDir,
5496
5951
  skillDirectories: config2.skillDirectories,
@@ -5500,9 +5955,7 @@ var CopilotClient = class {
5500
5955
  const { sessionId, workspacePath } = response;
5501
5956
  const session = new CopilotSession(sessionId, this.connection, workspacePath);
5502
5957
  session.registerTools(config2.tools);
5503
- if (config2.onPermissionRequest) {
5504
- session.registerPermissionHandler(config2.onPermissionRequest);
5505
- }
5958
+ session.registerPermissionHandler(config2.onPermissionRequest);
5506
5959
  if (config2.onUserInputRequest) {
5507
5960
  session.registerUserInputHandler(config2.onUserInputRequest);
5508
5961
  }
@@ -5527,15 +5980,21 @@ var CopilotClient = class {
5527
5980
  * @example
5528
5981
  * ```typescript
5529
5982
  * // Resume a previous session
5530
- * const session = await client.resumeSession("session-123");
5983
+ * const session = await client.resumeSession("session-123", { onPermissionRequest: approveAll });
5531
5984
  *
5532
5985
  * // Resume with new tools
5533
5986
  * const session = await client.resumeSession("session-123", {
5987
+ * onPermissionRequest: approveAll,
5534
5988
  * tools: [myNewTool]
5535
5989
  * });
5536
5990
  * ```
5537
5991
  */
5538
- async resumeSession(sessionId, config2 = {}) {
5992
+ async resumeSession(sessionId, config2) {
5993
+ if (!config2?.onPermissionRequest) {
5994
+ throw new Error(
5995
+ "An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
5996
+ );
5997
+ }
5539
5998
  if (!this.connection) {
5540
5999
  if (this.options.autoStart) {
5541
6000
  await this.start();
@@ -5545,6 +6004,7 @@ var CopilotClient = class {
5545
6004
  }
5546
6005
  const response = await this.connection.sendRequest("session.resume", {
5547
6006
  sessionId,
6007
+ clientName: config2.clientName,
5548
6008
  model: config2.model,
5549
6009
  reasoningEffort: config2.reasoningEffort,
5550
6010
  systemMessage: config2.systemMessage,
@@ -5553,16 +6013,18 @@ var CopilotClient = class {
5553
6013
  tools: config2.tools?.map((tool) => ({
5554
6014
  name: tool.name,
5555
6015
  description: tool.description,
5556
- parameters: toJsonSchema(tool.parameters)
6016
+ parameters: toJsonSchema(tool.parameters),
6017
+ overridesBuiltInTool: tool.overridesBuiltInTool
5557
6018
  })),
5558
6019
  provider: config2.provider,
5559
- requestPermission: !!config2.onPermissionRequest,
6020
+ requestPermission: true,
5560
6021
  requestUserInput: !!config2.onUserInputRequest,
5561
6022
  hooks: !!(config2.hooks && Object.values(config2.hooks).some(Boolean)),
5562
6023
  workingDirectory: config2.workingDirectory,
5563
6024
  configDir: config2.configDir,
5564
6025
  streaming: config2.streaming,
5565
6026
  mcpServers: config2.mcpServers,
6027
+ envValueMode: "direct",
5566
6028
  customAgents: config2.customAgents,
5567
6029
  skillDirectories: config2.skillDirectories,
5568
6030
  disabledSkills: config2.disabledSkills,
@@ -5572,9 +6034,7 @@ var CopilotClient = class {
5572
6034
  const { sessionId: resumedSessionId, workspacePath } = response;
5573
6035
  const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
5574
6036
  session.registerTools(config2.tools);
5575
- if (config2.onPermissionRequest) {
5576
- session.registerPermissionHandler(config2.onPermissionRequest);
5577
- }
6037
+ session.registerPermissionHandler(config2.onPermissionRequest);
5578
6038
  if (config2.onUserInputRequest) {
5579
6039
  session.registerUserInputHandler(config2.onUserInputRequest);
5580
6040
  }
@@ -5592,7 +6052,7 @@ var CopilotClient = class {
5592
6052
  * @example
5593
6053
  * ```typescript
5594
6054
  * if (client.getState() === "connected") {
5595
- * const session = await client.createSession();
6055
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
5596
6056
  * }
5597
6057
  * ```
5598
6058
  */
@@ -5670,22 +6130,29 @@ var CopilotClient = class {
5670
6130
  }
5671
6131
  }
5672
6132
  /**
5673
- * Verify that the server's protocol version matches the SDK's expected version
6133
+ * Verify that the server's protocol version is within the supported range
6134
+ * and store the negotiated version.
5674
6135
  */
5675
6136
  async verifyProtocolVersion() {
5676
- const expectedVersion = getSdkProtocolVersion();
5677
- const pingResult = await this.ping();
6137
+ const maxVersion = getSdkProtocolVersion();
6138
+ let pingResult;
6139
+ if (this.processExitPromise) {
6140
+ pingResult = await Promise.race([this.ping(), this.processExitPromise]);
6141
+ } else {
6142
+ pingResult = await this.ping();
6143
+ }
5678
6144
  const serverVersion = pingResult.protocolVersion;
5679
6145
  if (serverVersion === void 0) {
5680
6146
  throw new Error(
5681
- `SDK protocol version mismatch: SDK expects version ${expectedVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
6147
+ `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
5682
6148
  );
5683
6149
  }
5684
- if (serverVersion !== expectedVersion) {
6150
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > maxVersion) {
5685
6151
  throw new Error(
5686
- `SDK protocol version mismatch: SDK expects version ${expectedVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`
6152
+ `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`
5687
6153
  );
5688
6154
  }
6155
+ this.negotiatedProtocolVersion = serverVersion;
5689
6156
  }
5690
6157
  /**
5691
6158
  * Gets the ID of the most recently updated session.
@@ -5700,7 +6167,7 @@ var CopilotClient = class {
5700
6167
  * ```typescript
5701
6168
  * const lastId = await client.getLastSessionId();
5702
6169
  * if (lastId) {
5703
- * const session = await client.resumeSession(lastId);
6170
+ * const session = await client.resumeSession(lastId, { onPermissionRequest: approveAll });
5704
6171
  * }
5705
6172
  * ```
5706
6173
  */
@@ -5712,10 +6179,12 @@ var CopilotClient = class {
5712
6179
  return response.sessionId;
5713
6180
  }
5714
6181
  /**
5715
- * Deletes a session and its data from disk.
6182
+ * Permanently deletes a session and all its data from disk, including
6183
+ * conversation history, planning state, and artifacts.
5716
6184
  *
5717
- * This permanently removes the session and all its conversation history.
5718
- * The session cannot be resumed after deletion.
6185
+ * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
6186
+ * resources and preserves session data for later resumption, this method
6187
+ * is irreversible. The session cannot be resumed after deletion.
5719
6188
  *
5720
6189
  * @param sessionId - The ID of the session to delete
5721
6190
  * @returns A promise that resolves when the session is deleted
@@ -5740,33 +6209,31 @@ var CopilotClient = class {
5740
6209
  this.sessions.delete(sessionId);
5741
6210
  }
5742
6211
  /**
5743
- * Lists all available sessions known to the server.
6212
+ * List all available sessions.
5744
6213
  *
5745
- * Returns metadata about each session including ID, timestamps, and summary.
5746
- *
5747
- * @returns A promise that resolves with an array of session metadata
5748
- * @throws Error if the client is not connected
6214
+ * @param filter - Optional filter to limit returned sessions by context fields
5749
6215
  *
5750
6216
  * @example
5751
- * ```typescript
6217
+ * // List all sessions
5752
6218
  * const sessions = await client.listSessions();
5753
- * for (const session of sessions) {
5754
- * console.log(`${session.sessionId}: ${session.summary}`);
5755
- * }
5756
- * ```
6219
+ *
6220
+ * @example
6221
+ * // List sessions for a specific repository
6222
+ * const sessions = await client.listSessions({ repository: "owner/repo" });
5757
6223
  */
5758
- async listSessions() {
6224
+ async listSessions(filter) {
5759
6225
  if (!this.connection) {
5760
6226
  throw new Error("Client not connected");
5761
6227
  }
5762
- const response = await this.connection.sendRequest("session.list", {});
6228
+ const response = await this.connection.sendRequest("session.list", { filter });
5763
6229
  const { sessions } = response;
5764
6230
  return sessions.map((s) => ({
5765
6231
  sessionId: s.sessionId,
5766
6232
  startTime: new Date(s.startTime),
5767
6233
  modifiedTime: new Date(s.modifiedTime),
5768
6234
  summary: s.summary,
5769
- isRemote: s.isRemote
6235
+ isRemote: s.isRemote,
6236
+ context: s.context
5770
6237
  }));
5771
6238
  }
5772
6239
  /**
@@ -5845,6 +6312,7 @@ var CopilotClient = class {
5845
6312
  */
5846
6313
  async startCLIServer() {
5847
6314
  return new Promise((resolve3, reject) => {
6315
+ this.stderrBuffer = "";
5848
6316
  const args = [
5849
6317
  ...this.options.cliArgs,
5850
6318
  "--headless",
@@ -5876,16 +6344,18 @@ var CopilotClient = class {
5876
6344
  const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
5877
6345
  const isJsFile = this.options.cliPath.endsWith(".js");
5878
6346
  if (isJsFile) {
5879
- this.cliProcess = spawn(process.execPath, [this.options.cliPath, ...args], {
6347
+ this.cliProcess = spawn(getNodeExecPath(), [this.options.cliPath, ...args], {
5880
6348
  stdio: stdioConfig,
5881
6349
  cwd: this.options.cwd,
5882
- env: envWithoutNodeDebug
6350
+ env: envWithoutNodeDebug,
6351
+ windowsHide: true
5883
6352
  });
5884
6353
  } else {
5885
6354
  this.cliProcess = spawn(this.options.cliPath, args, {
5886
6355
  stdio: stdioConfig,
5887
6356
  cwd: this.options.cwd,
5888
- env: envWithoutNodeDebug
6357
+ env: envWithoutNodeDebug,
6358
+ windowsHide: true
5889
6359
  });
5890
6360
  }
5891
6361
  let stdout = "";
@@ -5905,6 +6375,7 @@ var CopilotClient = class {
5905
6375
  });
5906
6376
  }
5907
6377
  this.cliProcess.stderr?.on("data", (data) => {
6378
+ this.stderrBuffer += data.toString();
5908
6379
  const lines = data.toString().split("\n");
5909
6380
  for (const line of lines) {
5910
6381
  if (line.trim()) {
@@ -5916,13 +6387,54 @@ var CopilotClient = class {
5916
6387
  this.cliProcess.on("error", (error) => {
5917
6388
  if (!resolved) {
5918
6389
  resolved = true;
5919
- reject(new Error(`Failed to start CLI server: ${error.message}`));
6390
+ const stderrOutput = this.stderrBuffer.trim();
6391
+ if (stderrOutput) {
6392
+ reject(
6393
+ new Error(
6394
+ `Failed to start CLI server: ${error.message}
6395
+ stderr: ${stderrOutput}`
6396
+ )
6397
+ );
6398
+ } else {
6399
+ reject(new Error(`Failed to start CLI server: ${error.message}`));
6400
+ }
5920
6401
  }
5921
6402
  });
6403
+ this.processExitPromise = new Promise((_, rejectProcessExit) => {
6404
+ this.cliProcess.on("exit", (code) => {
6405
+ setTimeout(() => {
6406
+ const stderrOutput = this.stderrBuffer.trim();
6407
+ if (stderrOutput) {
6408
+ rejectProcessExit(
6409
+ new Error(
6410
+ `CLI server exited with code ${code}
6411
+ stderr: ${stderrOutput}`
6412
+ )
6413
+ );
6414
+ } else {
6415
+ rejectProcessExit(
6416
+ new Error(`CLI server exited unexpectedly with code ${code}`)
6417
+ );
6418
+ }
6419
+ }, 50);
6420
+ });
6421
+ });
6422
+ this.processExitPromise.catch(() => {
6423
+ });
5922
6424
  this.cliProcess.on("exit", (code) => {
5923
6425
  if (!resolved) {
5924
6426
  resolved = true;
5925
- reject(new Error(`CLI server exited with code ${code}`));
6427
+ const stderrOutput = this.stderrBuffer.trim();
6428
+ if (stderrOutput) {
6429
+ reject(
6430
+ new Error(
6431
+ `CLI server exited with code ${code}
6432
+ stderr: ${stderrOutput}`
6433
+ )
6434
+ );
6435
+ } else {
6436
+ reject(new Error(`CLI server exited with code ${code}`));
6437
+ }
5926
6438
  } else if (this.options.autoRestart && this.state === "connected") {
5927
6439
  void this.reconnect();
5928
6440
  }
@@ -5939,16 +6451,18 @@ var CopilotClient = class {
5939
6451
  * Connect to the CLI server (via socket or stdio)
5940
6452
  */
5941
6453
  async connectToServer() {
5942
- if (this.options.useStdio) {
5943
- return this.connectViaStdio();
6454
+ if (this.options.isChildProcess) {
6455
+ return this.connectToParentProcessViaStdio();
6456
+ } else if (this.options.useStdio) {
6457
+ return this.connectToChildProcessViaStdio();
5944
6458
  } else {
5945
6459
  return this.connectViaTcp();
5946
6460
  }
5947
6461
  }
5948
6462
  /**
5949
- * Connect via stdio pipes
6463
+ * Connect to child via stdio pipes
5950
6464
  */
5951
- async connectViaStdio() {
6465
+ async connectToChildProcessViaStdio() {
5952
6466
  if (!this.cliProcess) {
5953
6467
  throw new Error("CLI process not started");
5954
6468
  }
@@ -5957,9 +6471,23 @@ var CopilotClient = class {
5957
6471
  throw err;
5958
6472
  }
5959
6473
  });
5960
- this.connection = (0, import_node.createMessageConnection)(
5961
- new import_node.StreamMessageReader(this.cliProcess.stdout),
5962
- new import_node.StreamMessageWriter(this.cliProcess.stdin)
6474
+ this.connection = (0, import_node2.createMessageConnection)(
6475
+ new import_node2.StreamMessageReader(this.cliProcess.stdout),
6476
+ new import_node2.StreamMessageWriter(this.cliProcess.stdin)
6477
+ );
6478
+ this.attachConnectionHandlers();
6479
+ this.connection.listen();
6480
+ }
6481
+ /**
6482
+ * Connect to parent via stdio pipes
6483
+ */
6484
+ async connectToParentProcessViaStdio() {
6485
+ if (this.cliProcess) {
6486
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
6487
+ }
6488
+ this.connection = (0, import_node2.createMessageConnection)(
6489
+ new import_node2.StreamMessageReader(process.stdin),
6490
+ new import_node2.StreamMessageWriter(process.stdout)
5963
6491
  );
5964
6492
  this.attachConnectionHandlers();
5965
6493
  this.connection.listen();
@@ -5974,9 +6502,9 @@ var CopilotClient = class {
5974
6502
  return new Promise((resolve3, reject) => {
5975
6503
  this.socket = new Socket();
5976
6504
  this.socket.connect(this.actualPort, this.actualHost, () => {
5977
- this.connection = (0, import_node.createMessageConnection)(
5978
- new import_node.StreamMessageReader(this.socket),
5979
- new import_node.StreamMessageWriter(this.socket)
6505
+ this.connection = (0, import_node2.createMessageConnection)(
6506
+ new import_node2.StreamMessageReader(this.socket),
6507
+ new import_node2.StreamMessageWriter(this.socket)
5980
6508
  );
5981
6509
  this.attachConnectionHandlers();
5982
6510
  this.connection.listen();
@@ -5999,11 +6527,11 @@ var CopilotClient = class {
5999
6527
  });
6000
6528
  this.connection.onRequest(
6001
6529
  "tool.call",
6002
- async (params) => await this.handleToolCallRequest(params)
6530
+ async (params) => await this.handleToolCallRequestV2(params)
6003
6531
  );
6004
6532
  this.connection.onRequest(
6005
6533
  "permission.request",
6006
- async (params) => await this.handlePermissionRequest(params)
6534
+ async (params) => await this.handlePermissionRequestV2(params)
6007
6535
  );
6008
6536
  this.connection.onRequest(
6009
6537
  "userInput.request",
@@ -6051,7 +6579,41 @@ var CopilotClient = class {
6051
6579
  }
6052
6580
  }
6053
6581
  }
6054
- async handleToolCallRequest(params) {
6582
+ async handleUserInputRequest(params) {
6583
+ if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
6584
+ throw new Error("Invalid user input request payload");
6585
+ }
6586
+ const session = this.sessions.get(params.sessionId);
6587
+ if (!session) {
6588
+ throw new Error(`Session not found: ${params.sessionId}`);
6589
+ }
6590
+ const result = await session._handleUserInputRequest({
6591
+ question: params.question,
6592
+ choices: params.choices,
6593
+ allowFreeform: params.allowFreeform
6594
+ });
6595
+ return result;
6596
+ }
6597
+ async handleHooksInvoke(params) {
6598
+ if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
6599
+ throw new Error("Invalid hooks invoke payload");
6600
+ }
6601
+ const session = this.sessions.get(params.sessionId);
6602
+ if (!session) {
6603
+ throw new Error(`Session not found: ${params.sessionId}`);
6604
+ }
6605
+ const output = await session._handleHooksInvoke(params.hookType, params.input);
6606
+ return { output };
6607
+ }
6608
+ // ========================================================================
6609
+ // Protocol v2 backward-compatibility adapters
6610
+ // ========================================================================
6611
+ /**
6612
+ * Handles a v2-style tool.call RPC request from the server.
6613
+ * Looks up the session and tool handler, executes it, and returns the result
6614
+ * in the v2 response format.
6615
+ */
6616
+ async handleToolCallRequestV2(params) {
6055
6617
  if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
6056
6618
  throw new Error("Invalid tool call payload");
6057
6619
  }
@@ -6061,25 +6623,28 @@ var CopilotClient = class {
6061
6623
  }
6062
6624
  const handler = session.getToolHandler(params.toolName);
6063
6625
  if (!handler) {
6064
- return { result: this.buildUnsupportedToolResult(params.toolName) };
6626
+ return {
6627
+ result: {
6628
+ textResultForLlm: `Tool '${params.toolName}' is not supported by this client instance.`,
6629
+ resultType: "failure",
6630
+ error: `tool '${params.toolName}' not supported`,
6631
+ toolTelemetry: {}
6632
+ }
6633
+ };
6065
6634
  }
6066
- return await this.executeToolCall(handler, params);
6067
- }
6068
- async executeToolCall(handler, request) {
6069
6635
  try {
6070
6636
  const invocation = {
6071
- sessionId: request.sessionId,
6072
- toolCallId: request.toolCallId,
6073
- toolName: request.toolName,
6074
- arguments: request.arguments
6637
+ sessionId: params.sessionId,
6638
+ toolCallId: params.toolCallId,
6639
+ toolName: params.toolName,
6640
+ arguments: params.arguments
6075
6641
  };
6076
- const result = await handler(request.arguments, invocation);
6077
- return { result: this.normalizeToolResult(result) };
6642
+ const result = await handler(params.arguments, invocation);
6643
+ return { result: this.normalizeToolResultV2(result) };
6078
6644
  } catch (error) {
6079
6645
  const message = error instanceof Error ? error.message : String(error);
6080
6646
  return {
6081
6647
  result: {
6082
- // Don't expose detailed error information to the LLM for security reasons
6083
6648
  textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
6084
6649
  resultType: "failure",
6085
6650
  error: message,
@@ -6088,7 +6653,10 @@ var CopilotClient = class {
6088
6653
  };
6089
6654
  }
6090
6655
  }
6091
- async handlePermissionRequest(params) {
6656
+ /**
6657
+ * Handles a v2-style permission.request RPC request from the server.
6658
+ */
6659
+ async handlePermissionRequestV2(params) {
6092
6660
  if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
6093
6661
  throw new Error("Invalid permission request payload");
6094
6662
  }
@@ -6097,7 +6665,7 @@ var CopilotClient = class {
6097
6665
  throw new Error(`Session not found: ${params.sessionId}`);
6098
6666
  }
6099
6667
  try {
6100
- const result = await session._handlePermissionRequest(params.permissionRequest);
6668
+ const result = await session._handlePermissionRequestV2(params.permissionRequest);
6101
6669
  return { result };
6102
6670
  } catch (_error) {
6103
6671
  return {
@@ -6107,33 +6675,7 @@ var CopilotClient = class {
6107
6675
  };
6108
6676
  }
6109
6677
  }
6110
- async handleUserInputRequest(params) {
6111
- if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
6112
- throw new Error("Invalid user input request payload");
6113
- }
6114
- const session = this.sessions.get(params.sessionId);
6115
- if (!session) {
6116
- throw new Error(`Session not found: ${params.sessionId}`);
6117
- }
6118
- const result = await session._handleUserInputRequest({
6119
- question: params.question,
6120
- choices: params.choices,
6121
- allowFreeform: params.allowFreeform
6122
- });
6123
- return result;
6124
- }
6125
- async handleHooksInvoke(params) {
6126
- if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
6127
- throw new Error("Invalid hooks invoke payload");
6128
- }
6129
- const session = this.sessions.get(params.sessionId);
6130
- if (!session) {
6131
- throw new Error(`Session not found: ${params.sessionId}`);
6132
- }
6133
- const output = await session._handleHooksInvoke(params.hookType, params.input);
6134
- return { output };
6135
- }
6136
- normalizeToolResult(result) {
6678
+ normalizeToolResultV2(result) {
6137
6679
  if (result === void 0 || result === null) {
6138
6680
  return {
6139
6681
  textResultForLlm: "Tool returned no result",
@@ -6155,14 +6697,6 @@ var CopilotClient = class {
6155
6697
  isToolResultObject(value) {
6156
6698
  return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
6157
6699
  }
6158
- buildUnsupportedToolResult(toolName) {
6159
- return {
6160
- textResultForLlm: `Tool '${toolName}' is not supported by this client instance.`,
6161
- resultType: "failure",
6162
- error: `tool '${toolName}' not supported`,
6163
- toolTelemetry: {}
6164
- };
6165
- }
6166
6700
  /**
6167
6701
  * Attempt to reconnect to the server
6168
6702
  */
@@ -6176,6 +6710,38 @@ var CopilotClient = class {
6176
6710
  }
6177
6711
  };
6178
6712
 
6713
+ // node_modules/@github/copilot-sdk/dist/types.js
6714
+ var approveAll = () => ({ kind: "approved" });
6715
+
6716
+ // src/L1-infra/ai/copilot.ts
6717
+ import { existsSync as existsSync5 } from "fs";
6718
+ import { join as join6, dirname as dirname4 } from "path";
6719
+ import { createRequire as createRequire2 } from "module";
6720
+ function resolveCopilotCliPath() {
6721
+ const platform = process.platform;
6722
+ const arch = process.arch;
6723
+ const binaryName = platform === "win32" ? "copilot.exe" : "copilot";
6724
+ const platformPkg = `@github/copilot-${platform}-${arch}`;
6725
+ try {
6726
+ const require_ = createRequire2(import.meta.url);
6727
+ const searchPaths = require_.resolve.paths(platformPkg) ?? [];
6728
+ for (const base of searchPaths) {
6729
+ const candidate = join6(base, platformPkg, binaryName);
6730
+ if (existsSync5(candidate)) return candidate;
6731
+ }
6732
+ } catch {
6733
+ }
6734
+ let dir = dirname4(import.meta.dirname ?? __dirname);
6735
+ for (let i = 0; i < 10; i++) {
6736
+ const candidate = join6(dir, "node_modules", platformPkg, binaryName);
6737
+ if (existsSync5(candidate)) return candidate;
6738
+ const parent = dirname4(dir);
6739
+ if (parent === dir) break;
6740
+ dir = parent;
6741
+ }
6742
+ return void 0;
6743
+ }
6744
+
6179
6745
  // src/L2-clients/llm/ai.ts
6180
6746
  function createOpenAI(...args) {
6181
6747
  return new default4(...args);
@@ -6191,6 +6757,7 @@ function createCopilotClient(...args) {
6191
6757
  init_configLogger();
6192
6758
  var DEFAULT_MODEL = "claude-opus-4.5";
6193
6759
  var DEFAULT_TIMEOUT_MS = 3e5;
6760
+ var SESSION_CREATE_TIMEOUT_MS = 3e4;
6194
6761
  var CopilotProvider = class {
6195
6762
  name = "copilot";
6196
6763
  client = null;
@@ -6202,21 +6769,57 @@ var CopilotProvider = class {
6202
6769
  }
6203
6770
  async createSession(config2) {
6204
6771
  if (!this.client) {
6205
- this.client = createCopilotClient({ autoStart: true, logLevel: "error" });
6772
+ const cliPath = resolveCopilotCliPath();
6773
+ if (cliPath) {
6774
+ logger_default.info(`[CopilotProvider] Using native CLI binary: ${cliPath}`);
6775
+ }
6776
+ this.client = createCopilotClient({
6777
+ autoStart: true,
6778
+ autoRestart: true,
6779
+ logLevel: "error",
6780
+ env: buildChildEnv(),
6781
+ ...cliPath ? { cliPath } : {}
6782
+ });
6206
6783
  }
6207
- const copilotSession = await this.client.createSession({
6208
- model: config2.model,
6209
- mcpServers: config2.mcpServers,
6210
- systemMessage: { mode: "replace", content: config2.systemPrompt },
6211
- tools: config2.tools.map((t) => ({
6212
- name: t.name,
6213
- description: t.description,
6214
- parameters: t.parameters,
6215
- handler: t.handler
6216
- })),
6217
- streaming: config2.streaming ?? true,
6218
- onUserInputRequest: config2.onUserInputRequest ? (request) => config2.onUserInputRequest(request) : void 0
6219
- });
6784
+ logger_default.info("[CopilotProvider] Creating session\u2026");
6785
+ let copilotSession;
6786
+ try {
6787
+ copilotSession = await new Promise((resolve3, reject) => {
6788
+ const timeoutId = setTimeout(
6789
+ () => reject(new Error(
6790
+ `[CopilotProvider] createSession timed out after ${SESSION_CREATE_TIMEOUT_MS / 1e3}s \u2014 the Copilot SDK language server may not be reachable. Check GitHub authentication and network connectivity.`
6791
+ )),
6792
+ SESSION_CREATE_TIMEOUT_MS
6793
+ );
6794
+ this.client.createSession({
6795
+ model: config2.model,
6796
+ mcpServers: config2.mcpServers,
6797
+ systemMessage: { mode: "replace", content: config2.systemPrompt },
6798
+ tools: config2.tools.map((t) => ({
6799
+ name: t.name,
6800
+ description: t.description,
6801
+ parameters: t.parameters,
6802
+ handler: t.handler
6803
+ })),
6804
+ streaming: config2.streaming ?? true,
6805
+ onPermissionRequest: approveAll,
6806
+ onUserInputRequest: config2.onUserInputRequest ? (request) => config2.onUserInputRequest(request) : void 0
6807
+ }).then(
6808
+ (session) => {
6809
+ clearTimeout(timeoutId);
6810
+ resolve3(session);
6811
+ },
6812
+ (err) => {
6813
+ clearTimeout(timeoutId);
6814
+ reject(err);
6815
+ }
6816
+ );
6817
+ });
6818
+ } catch (err) {
6819
+ this.client = null;
6820
+ throw err;
6821
+ }
6822
+ logger_default.info("[CopilotProvider] Session created successfully");
6220
6823
  return new CopilotSessionWrapper(
6221
6824
  copilotSession,
6222
6825
  config2.timeoutMs ?? DEFAULT_TIMEOUT_MS
@@ -6361,6 +6964,15 @@ var CopilotSessionWrapper = class {
6361
6964
  }
6362
6965
  }
6363
6966
  };
6967
+ function buildChildEnv() {
6968
+ const env = { ...process.env };
6969
+ const flag = "--disable-warning=ExperimentalWarning";
6970
+ const current = env.NODE_OPTIONS ?? "";
6971
+ if (!current.includes(flag)) {
6972
+ env.NODE_OPTIONS = current ? `${current} ${flag}` : flag;
6973
+ }
6974
+ return env;
6975
+ }
6364
6976
 
6365
6977
  // src/L0-pure/pricing/pricing.ts
6366
6978
  var COPILOT_PRU_OVERAGE_RATE = 0.04;
@@ -6611,7 +7223,8 @@ var OpenAIProvider = class {
6611
7223
  return "gpt-4o";
6612
7224
  }
6613
7225
  async createSession(config2) {
6614
- const client = createOpenAI();
7226
+ const appConfig = getConfig();
7227
+ const client = createOpenAI({ apiKey: appConfig.OPENAI_API_KEY });
6615
7228
  const model = config2.model ?? this.getDefaultModel();
6616
7229
  logger_default.info(`OpenAI session created (model=${model}, tools=${config2.tools.length})`);
6617
7230
  return new OpenAISession(client, config2, model);
@@ -6940,16 +7553,10 @@ var LateApiClient = class {
6940
7553
  return data.accounts ?? [];
6941
7554
  }
6942
7555
  async getScheduledPosts(platform) {
6943
- const params = new URLSearchParams({ status: "scheduled" });
6944
- if (platform) params.set("platform", platform);
6945
- const data = await this.request(`/posts?${params}`);
6946
- return data.posts ?? [];
7556
+ return this.listPosts({ status: "scheduled", platform });
6947
7557
  }
6948
7558
  async getDraftPosts(platform) {
6949
- const params = new URLSearchParams({ status: "draft" });
6950
- if (platform) params.set("platform", platform);
6951
- const data = await this.request(`/posts?${params}`);
6952
- return data.posts ?? [];
7559
+ return this.listPosts({ status: "draft", platform });
6953
7560
  }
6954
7561
  async createPost(params) {
6955
7562
  const data = await this.request("/posts", {
@@ -7055,6 +7662,7 @@ function createLateApiClient(...args) {
7055
7662
  // src/L2-clients/scheduleStore/scheduleStore.ts
7056
7663
  init_fileSystem();
7057
7664
  init_paths();
7665
+ init_globalConfig();
7058
7666
  async function readScheduleFile(filePath) {
7059
7667
  return readTextFile(filePath);
7060
7668
  }
@@ -7066,7 +7674,10 @@ async function writeScheduleFile(filePath, content) {
7066
7674
  });
7067
7675
  }
7068
7676
  function resolveSchedulePath(configPath) {
7069
- return configPath ?? join(process.cwd(), "schedule.json");
7677
+ if (configPath) return configPath;
7678
+ const globalPath = getGlobalConfigValue("defaults", "scheduleConfig");
7679
+ if (globalPath) return globalPath;
7680
+ return join(process.cwd(), "schedule.json");
7070
7681
  }
7071
7682
 
7072
7683
  // src/L3-services/scheduler/scheduleConfig.ts
@@ -7331,7 +7942,7 @@ function getPlatformSchedule(platform, clipType) {
7331
7942
  avoidDays: sub.avoidDays
7332
7943
  };
7333
7944
  }
7334
- if (clipType && schedule.slots.length === 0 && schedule.byClipType) {
7945
+ if (schedule.slots.length === 0 && schedule.byClipType) {
7335
7946
  const allSlots = [];
7336
7947
  const allAvoidDays = /* @__PURE__ */ new Set();
7337
7948
  for (const sub of Object.values(schedule.byClipType)) {
@@ -7352,403 +7963,23 @@ function getDisplacementConfig() {
7352
7963
  return cachedConfig?.displacement ?? { ...defaultDisplacement };
7353
7964
  }
7354
7965
 
7355
- // src/L3-services/postStore/postStore.ts
7356
- init_types();
7357
- init_environment();
7358
- init_configLogger();
7359
- init_fileSystem();
7360
- init_paths();
7361
- function getQueueDir() {
7362
- const { OUTPUT_DIR } = getConfig();
7363
- return join(OUTPUT_DIR, "publish-queue");
7364
- }
7365
- function getPublishedDir() {
7366
- const { OUTPUT_DIR } = getConfig();
7367
- return join(OUTPUT_DIR, "published");
7368
- }
7369
- async function readQueueItem(folderPath, id) {
7370
- const metadataPath = join(folderPath, "metadata.json");
7371
- const postPath = join(folderPath, "post.md");
7372
- try {
7373
- const metadataRaw = await readTextFile(metadataPath);
7374
- const metadata = JSON.parse(metadataRaw);
7375
- let postContent = "";
7376
- try {
7377
- postContent = await readTextFile(postPath);
7378
- } catch {
7379
- logger_default.debug(`No post.md found for ${String(id).replace(/[\r\n]/g, "")}`);
7380
- }
7381
- const videoPath = join(folderPath, "media.mp4");
7382
- const imagePath = join(folderPath, "media.png");
7383
- let mediaPath = null;
7384
- let hasMedia = false;
7385
- if (await fileExists(videoPath)) {
7386
- mediaPath = videoPath;
7387
- hasMedia = true;
7388
- } else if (await fileExists(imagePath)) {
7389
- mediaPath = imagePath;
7390
- hasMedia = true;
7391
- }
7392
- return {
7393
- id,
7394
- metadata,
7395
- postContent,
7396
- hasMedia,
7397
- mediaPath,
7398
- folderPath
7399
- };
7400
- } catch (err) {
7401
- logger_default.debug(`Failed to read queue item ${String(id).replace(/[\r\n]/g, "")}: ${String(err).replace(/[\r\n]/g, "")}`);
7402
- return null;
7403
- }
7404
- }
7405
- async function getPendingItems() {
7406
- const queueDir = getQueueDir();
7407
- await ensureDirectory(queueDir);
7408
- let entries;
7409
- try {
7410
- const dirents = await listDirectoryWithTypes(queueDir);
7411
- entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
7412
- } catch {
7413
- return [];
7414
- }
7415
- const items = [];
7416
- for (const name of entries) {
7417
- const item = await readQueueItem(join(queueDir, name), name);
7418
- if (item) items.push(item);
7419
- }
7420
- items.sort((a, b) => {
7421
- if (a.hasMedia !== b.hasMedia) return a.hasMedia ? -1 : 1;
7422
- return a.metadata.createdAt.localeCompare(b.metadata.createdAt);
7423
- });
7424
- return items;
7425
- }
7426
- async function createItem(id, metadata, postContent, mediaSourcePath) {
7427
- if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
7428
- throw new Error(`Invalid ID format: ${id}`);
7429
- }
7430
- const folderPath = join(getQueueDir(), basename(id));
7431
- await ensureDirectory(folderPath);
7432
- await writeJsonFile(join(folderPath, "metadata.json"), metadata);
7433
- await writeTextFile(join(folderPath, "post.md"), postContent);
7434
- let hasMedia = false;
7435
- const ext = mediaSourcePath ? extname(mediaSourcePath) : ".mp4";
7436
- const mediaFilename = `media${ext}`;
7437
- const mediaPath = join(folderPath, mediaFilename);
7438
- if (mediaSourcePath) {
7439
- await copyFile(mediaSourcePath, mediaPath);
7440
- hasMedia = true;
7441
- }
7442
- logger_default.debug(`Created queue item: ${String(id).replace(/[\r\n]/g, "")}`);
7443
- return {
7444
- id,
7445
- metadata,
7446
- postContent,
7447
- hasMedia,
7448
- mediaPath: hasMedia ? mediaPath : null,
7449
- folderPath
7450
- };
7451
- }
7452
- async function getPublishedItems() {
7453
- const publishedDir = getPublishedDir();
7454
- await ensureDirectory(publishedDir);
7455
- let entries;
7456
- try {
7457
- const dirents = await listDirectoryWithTypes(publishedDir);
7458
- entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
7459
- } catch {
7460
- return [];
7461
- }
7462
- const items = [];
7463
- for (const name of entries) {
7464
- const item = await readQueueItem(join(publishedDir, name), name);
7465
- if (item) items.push(item);
7466
- }
7467
- items.sort((a, b) => a.metadata.createdAt.localeCompare(b.metadata.createdAt));
7468
- return items;
7469
- }
7470
- async function getScheduledItemsByIdeaIds(ideaIds) {
7471
- if (ideaIds.length === 0) return [];
7472
- const ideaIdSet = new Set(ideaIds);
7473
- const [pendingItems, publishedItems] = await Promise.all([
7474
- getPendingItems(),
7475
- getPublishedItems()
7476
- ]);
7477
- return [...pendingItems, ...publishedItems].filter(
7478
- (item) => item.metadata.ideaIds?.some((id) => ideaIdSet.has(id)) ?? false
7479
- );
7480
- }
7481
- async function getPublishedItemByLatePostId(latePostId) {
7482
- const publishedItems = await getPublishedItems();
7483
- return publishedItems.find((item) => item.metadata.latePostId === latePostId) ?? null;
7484
- }
7485
- async function itemExists(id) {
7486
- if (!id || !/^[a-zA-Z0-9_-]+$/.test(id)) {
7487
- throw new Error(`Invalid ID format: ${id}`);
7488
- }
7489
- if (await fileExists(join(getQueueDir(), basename(id)))) {
7490
- return "pending";
7491
- }
7492
- if (await fileExists(join(getPublishedDir(), basename(id)))) {
7493
- return "published";
7494
- }
7495
- return null;
7496
- }
7497
-
7498
7966
  // src/L3-services/scheduler/realign.ts
7967
+ init_postStore();
7499
7968
  init_configLogger();
7500
- function getTimezoneOffset(timezone, date) {
7501
- const formatter = new Intl.DateTimeFormat("en-US", {
7502
- timeZone: timezone,
7503
- timeZoneName: "longOffset"
7504
- });
7505
- const parts = formatter.formatToParts(date);
7506
- const tzPart = parts.find((p) => p.type === "timeZoneName");
7507
- const match = tzPart?.value?.match(/GMT([+-]\d{2}:\d{2})/);
7508
- if (match) return match[1];
7509
- if (tzPart?.value === "GMT") return "+00:00";
7510
- return "+00:00";
7511
- }
7512
- function buildSlotDatetime(date, time, timezone) {
7513
- const formatter = new Intl.DateTimeFormat("en-US", {
7514
- timeZone: timezone,
7515
- year: "numeric",
7516
- month: "2-digit",
7517
- day: "2-digit"
7518
- });
7519
- const parts = formatter.formatToParts(date);
7520
- const year = parts.find((p) => p.type === "year")?.value ?? String(date.getFullYear());
7521
- const month = (parts.find((p) => p.type === "month")?.value ?? String(date.getMonth() + 1)).padStart(2, "0");
7522
- const day = (parts.find((p) => p.type === "day")?.value ?? String(date.getDate())).padStart(2, "0");
7523
- const offset = getTimezoneOffset(timezone, date);
7524
- return `${year}-${month}-${day}T${time}:00${offset}`;
7525
- }
7526
- function getDayOfWeekInTimezone(date, timezone) {
7527
- const formatter = new Intl.DateTimeFormat("en-US", {
7528
- timeZone: timezone,
7529
- weekday: "short"
7530
- });
7531
- const short = formatter.format(date).toLowerCase().slice(0, 3);
7532
- const map = {
7533
- sun: "sun",
7534
- mon: "mon",
7535
- tue: "tue",
7536
- wed: "wed",
7537
- thu: "thu",
7538
- fri: "fri",
7539
- sat: "sat"
7540
- };
7541
- return map[short] ?? "mon";
7542
- }
7543
- var PLATFORM_ALIASES2 = { twitter: "x" };
7544
- function normalizeSchedulePlatform(platform) {
7545
- return PLATFORM_ALIASES2[platform] ?? platform;
7546
- }
7547
- function normalizeContent(content) {
7548
- return content.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 200);
7549
- }
7550
- async function buildClipTypeMaps() {
7551
- const published = await getPublishedItems();
7552
- const byLatePostId = /* @__PURE__ */ new Map();
7553
- const byContent = /* @__PURE__ */ new Map();
7554
- for (const item of published) {
7555
- if (item.metadata.latePostId) {
7556
- byLatePostId.set(item.metadata.latePostId, item.metadata.clipType);
7557
- }
7558
- if (item.postContent) {
7559
- const contentKey = `${item.metadata.platform}::${normalizeContent(item.postContent)}`;
7560
- byContent.set(contentKey, item.metadata.clipType);
7561
- }
7562
- }
7563
- logger_default.debug(`Built clipType maps: ${byLatePostId.size} by latePostId, ${byContent.size} by content`);
7564
- return { byLatePostId, byContent };
7565
- }
7566
- async function fetchAllPosts(client, statuses, platform) {
7567
- const allPosts = [];
7568
- for (const status of statuses) {
7569
- const posts = await client.listPosts({ status, platform });
7570
- allPosts.push(...posts);
7571
- logger_default.info(`Fetched ${posts.length} ${status} post(s)${platform ? ` for ${platform}` : ""}`);
7572
- }
7573
- return allPosts;
7574
- }
7575
- function generateSlots(platform, clipType, count, bookedMs, timezone) {
7576
- const schedule = getPlatformSchedule(platform, clipType);
7577
- if (!schedule || schedule.slots.length === 0) {
7578
- logger_default.warn(`No schedule slots for ${platform}/${clipType}`);
7579
- return [];
7580
- }
7581
- const available = [];
7582
- const now = /* @__PURE__ */ new Date();
7583
- const nowMs = now.getTime();
7584
- for (let dayOffset = 0; dayOffset < 730 && available.length < count; dayOffset++) {
7585
- const day = new Date(now);
7586
- day.setDate(day.getDate() + dayOffset);
7587
- const dayOfWeek = getDayOfWeekInTimezone(day, timezone);
7588
- if (schedule.avoidDays.includes(dayOfWeek)) continue;
7589
- for (const slot of schedule.slots) {
7590
- if (available.length >= count) break;
7591
- if (!slot.days.includes(dayOfWeek)) continue;
7592
- const iso = buildSlotDatetime(day, slot.time, timezone);
7593
- const ms = new Date(iso).getTime();
7594
- if (ms <= nowMs) continue;
7595
- if (!bookedMs.has(ms)) {
7596
- available.push(iso);
7597
- bookedMs.add(ms);
7598
- }
7599
- }
7600
- }
7601
- return available;
7602
- }
7603
- async function buildRealignPlan(options = {}) {
7604
- const config2 = await loadScheduleConfig();
7605
- const { timezone } = config2;
7606
- const client = new LateApiClient();
7607
- const statuses = ["scheduled", "draft", "cancelled", "failed"];
7608
- const allPosts = await fetchAllPosts(client, statuses, options.platform);
7609
- if (allPosts.length === 0) {
7610
- return { posts: [], toCancel: [], skipped: 0, unmatched: 0, totalFetched: 0 };
7611
- }
7612
- const { byLatePostId, byContent } = options.clipTypeMaps ?? await buildClipTypeMaps();
7613
- const grouped = /* @__PURE__ */ new Map();
7614
- let unmatched = 0;
7615
- let contentMatched = 0;
7616
- for (const post of allPosts) {
7617
- const platform = post.platforms[0]?.platform;
7618
- if (!platform) continue;
7619
- let clipType = byLatePostId.get(post._id) ?? null;
7620
- if (!clipType && post.content) {
7621
- const contentKey = `${platform}::${normalizeContent(post.content)}`;
7622
- clipType = byContent.get(contentKey) ?? null;
7623
- if (clipType) contentMatched++;
7624
- }
7625
- if (!clipType) {
7626
- clipType = "short";
7627
- unmatched++;
7628
- }
7629
- const key = `${platform}::${clipType}`;
7630
- if (!grouped.has(key)) grouped.set(key, []);
7631
- grouped.get(key).push({ post, platform, clipType });
7632
- }
7633
- const bookedMs = /* @__PURE__ */ new Set();
7634
- if (contentMatched > 0) {
7635
- logger_default.info(`${contentMatched} post(s) matched by content fallback (no latePostId)`);
7636
- }
7637
- const result = [];
7638
- const toCancel = [];
7639
- let skipped = 0;
7640
- for (const [key, posts] of grouped) {
7641
- const [platform, clipType] = key.split("::");
7642
- const schedulePlatform = normalizeSchedulePlatform(platform);
7643
- const schedule = getPlatformSchedule(schedulePlatform, clipType);
7644
- const hasSlots = schedule && schedule.slots.length > 0;
7645
- if (!hasSlots) {
7646
- for (const { post, clipType: ct } of posts) {
7647
- if (post.status === "cancelled") continue;
7648
- toCancel.push({
7649
- post,
7650
- platform,
7651
- clipType: ct,
7652
- reason: `No schedule slots for ${schedulePlatform}/${clipType}`
7653
- });
7654
- }
7655
- continue;
7656
- }
7657
- posts.sort((a, b) => {
7658
- const aTime = a.post.scheduledFor ? new Date(a.post.scheduledFor).getTime() : Infinity;
7659
- const bTime = b.post.scheduledFor ? new Date(b.post.scheduledFor).getTime() : Infinity;
7660
- return aTime - bTime;
7661
- });
7662
- const slots = generateSlots(schedulePlatform, clipType, posts.length, bookedMs, timezone);
7663
- for (let i = 0; i < posts.length; i++) {
7664
- const { post } = posts[i];
7665
- const newSlot = slots[i];
7666
- if (!newSlot) {
7667
- if (post.status !== "cancelled") {
7668
- toCancel.push({
7669
- post,
7670
- platform,
7671
- clipType: posts[i].clipType,
7672
- reason: `No more available slots for ${schedulePlatform}/${clipType}`
7673
- });
7674
- }
7675
- continue;
7676
- }
7677
- const currentMs = post.scheduledFor ? new Date(post.scheduledFor).getTime() : 0;
7678
- const newMs = new Date(newSlot).getTime();
7679
- if (currentMs === newMs && post.status === "scheduled") {
7680
- skipped++;
7681
- continue;
7682
- }
7683
- result.push({
7684
- post,
7685
- platform,
7686
- clipType: posts[i].clipType,
7687
- oldScheduledFor: post.scheduledFor ?? null,
7688
- newScheduledFor: newSlot
7689
- });
7690
- }
7691
- }
7692
- result.sort((a, b) => new Date(a.newScheduledFor).getTime() - new Date(b.newScheduledFor).getTime());
7693
- return { posts: result, toCancel, skipped, unmatched, totalFetched: allPosts.length };
7694
- }
7695
- async function executeRealignPlan(plan, onProgress) {
7696
- const client = new LateApiClient();
7697
- let updated = 0;
7698
- let cancelled = 0;
7699
- let failed = 0;
7700
- const errors = [];
7701
- const totalOps = plan.toCancel.length + plan.posts.length;
7702
- let completed = 0;
7703
- for (const entry of plan.toCancel) {
7704
- completed++;
7705
- try {
7706
- await client.updatePost(entry.post._id, { status: "cancelled" });
7707
- cancelled++;
7708
- const preview = entry.post.content.slice(0, 40).replace(/\n/g, " ");
7709
- logger_default.info(`[${completed}/${totalOps}] \u{1F6AB} Cancelled ${entry.platform}/${entry.clipType}: "${preview}..."`);
7710
- onProgress?.(completed, totalOps, "cancelling");
7711
- await new Promise((r) => setTimeout(r, 300));
7712
- } catch (err) {
7713
- const msg = err instanceof Error ? err.message : String(err);
7714
- errors.push({ postId: entry.post._id, error: msg });
7715
- failed++;
7716
- logger_default.error(`[${completed}/${totalOps}] \u274C Failed to cancel ${entry.post._id}: ${msg}`);
7717
- }
7718
- }
7719
- for (const entry of plan.posts) {
7720
- completed++;
7721
- try {
7722
- await client.schedulePost(entry.post._id, entry.newScheduledFor);
7723
- updated++;
7724
- const preview = entry.post.content.slice(0, 40).replace(/\n/g, " ");
7725
- logger_default.info(`[${completed}/${totalOps}] \u2705 ${entry.platform}/${entry.clipType}: "${preview}..." \u2192 ${entry.newScheduledFor}`);
7726
- onProgress?.(completed, totalOps, "updating");
7727
- await new Promise((r) => setTimeout(r, 300));
7728
- } catch (err) {
7729
- const msg = err instanceof Error ? err.message : String(err);
7730
- errors.push({ postId: entry.post._id, error: msg });
7731
- failed++;
7732
- logger_default.error(`[${completed}/${totalOps}] \u274C Failed to update ${entry.post._id}: ${msg}`);
7733
- }
7734
- }
7735
- return { updated, cancelled, failed, errors };
7736
- }
7737
7969
 
7738
7970
  // src/L3-services/scheduler/scheduler.ts
7739
7971
  init_configLogger();
7740
- function normalizeDateTime(isoString) {
7741
- return new Date(isoString).getTime();
7742
- }
7743
- var CHUNK_DAYS = 14;
7972
+ init_postStore();
7744
7973
  var MAX_LOOKAHEAD_DAYS = 730;
7745
- var DEFAULT_IDEA_WINDOW_DAYS = 14;
7746
7974
  var DAY_MS = 24 * 60 * 60 * 1e3;
7747
7975
  var HOUR_MS = 60 * 60 * 1e3;
7976
+ function normalizeDateTime(isoString) {
7977
+ return new Date(isoString).getTime();
7978
+ }
7748
7979
  function sanitizeLogValue(value) {
7749
7980
  return value.replace(/[\r\n]/g, "");
7750
7981
  }
7751
- function getTimezoneOffset2(timezone, date) {
7982
+ function getTimezoneOffset(timezone, date) {
7752
7983
  const formatter = new Intl.DateTimeFormat("en-US", {
7753
7984
  timeZone: timezone,
7754
7985
  timeZoneName: "longOffset"
@@ -7763,7 +7994,7 @@ function getTimezoneOffset2(timezone, date) {
7763
7994
  );
7764
7995
  return "+00:00";
7765
7996
  }
7766
- function buildSlotDatetime2(date, time, timezone) {
7997
+ function buildSlotDatetime(date, time, timezone) {
7767
7998
  const formatter = new Intl.DateTimeFormat("en-US", {
7768
7999
  timeZone: timezone,
7769
8000
  year: "numeric",
@@ -7777,10 +8008,10 @@ function buildSlotDatetime2(date, time, timezone) {
7777
8008
  const year = yearPart ?? String(date.getFullYear());
7778
8009
  const month = (monthPart ?? String(date.getMonth() + 1)).padStart(2, "0");
7779
8010
  const day = (dayPart ?? String(date.getDate())).padStart(2, "0");
7780
- const offset = getTimezoneOffset2(timezone, date);
8011
+ const offset = getTimezoneOffset(timezone, date);
7781
8012
  return `${year}-${month}-${day}T${time}:00${offset}`;
7782
8013
  }
7783
- function getDayOfWeekInTimezone2(date, timezone) {
8014
+ function getDayOfWeekInTimezone(date, timezone) {
7784
8015
  const formatter = new Intl.DateTimeFormat("en-US", {
7785
8016
  timeZone: timezone,
7786
8017
  weekday: "short"
@@ -7807,22 +8038,30 @@ async function fetchScheduledPostsSafe(platform) {
7807
8038
  return [];
7808
8039
  }
7809
8040
  }
7810
- async function buildBookedSlots(platform) {
8041
+ async function buildBookedMap(platform) {
7811
8042
  const [latePosts, publishedItems] = await Promise.all([
7812
8043
  fetchScheduledPostsSafe(platform),
7813
8044
  getPublishedItems()
7814
8045
  ]);
7815
- const slots = [];
8046
+ const ideaLinkedPostIds = /* @__PURE__ */ new Set();
8047
+ for (const item of publishedItems) {
8048
+ if (item.metadata.latePostId && item.metadata.ideaIds?.length) {
8049
+ ideaLinkedPostIds.add(item.metadata.latePostId);
8050
+ }
8051
+ }
8052
+ const map = /* @__PURE__ */ new Map();
7816
8053
  for (const post of latePosts) {
7817
8054
  if (!post.scheduledFor) continue;
7818
8055
  for (const scheduledPlatform of post.platforms) {
7819
8056
  if (!platform || scheduledPlatform.platform === platform) {
7820
- slots.push({
8057
+ const ms = normalizeDateTime(post.scheduledFor);
8058
+ map.set(ms, {
7821
8059
  scheduledFor: post.scheduledFor,
7822
8060
  source: "late",
7823
8061
  postId: post._id,
7824
8062
  platform: scheduledPlatform.platform,
7825
- status: post.status
8063
+ status: post.status,
8064
+ ideaLinked: ideaLinkedPostIds.has(post._id)
7826
8065
  });
7827
8066
  }
7828
8067
  }
@@ -7830,207 +8069,168 @@ async function buildBookedSlots(platform) {
7830
8069
  for (const item of publishedItems) {
7831
8070
  if (platform && item.metadata.platform !== platform) continue;
7832
8071
  if (!item.metadata.scheduledFor) continue;
7833
- slots.push({
7834
- scheduledFor: item.metadata.scheduledFor,
7835
- source: "local",
7836
- itemId: item.id,
7837
- platform: item.metadata.platform
7838
- });
8072
+ const ms = normalizeDateTime(item.metadata.scheduledFor);
8073
+ if (!map.has(ms)) {
8074
+ map.set(ms, {
8075
+ scheduledFor: item.metadata.scheduledFor,
8076
+ source: "local",
8077
+ itemId: item.id,
8078
+ platform: item.metadata.platform,
8079
+ ideaLinked: Boolean(item.metadata.ideaIds?.length)
8080
+ });
8081
+ }
7839
8082
  }
7840
- return slots;
8083
+ return map;
7841
8084
  }
7842
- function buildIdeaReferences(sameIdeaPosts, allBookedSlots) {
8085
+ async function getIdeaLinkedLatePostIds() {
8086
+ const publishedItems = await getPublishedItems();
8087
+ const ids = /* @__PURE__ */ new Set();
8088
+ for (const item of publishedItems) {
8089
+ if (item.metadata.latePostId && item.metadata.ideaIds?.length) {
8090
+ ids.add(item.metadata.latePostId);
8091
+ }
8092
+ }
8093
+ return ids;
8094
+ }
8095
+ function* generateTimeslots(platformConfig, timezone, fromMs, maxMs) {
8096
+ const baseDate = new Date(fromMs);
8097
+ const upperMs = maxMs ?? fromMs + MAX_LOOKAHEAD_DAYS * DAY_MS;
8098
+ for (let dayOffset = 0; dayOffset <= MAX_LOOKAHEAD_DAYS; dayOffset++) {
8099
+ const day = new Date(baseDate);
8100
+ day.setDate(day.getDate() + dayOffset);
8101
+ const dayOfWeek = getDayOfWeekInTimezone(day, timezone);
8102
+ if (platformConfig.avoidDays.includes(dayOfWeek)) continue;
8103
+ const dayCandidates = [];
8104
+ for (const slot of platformConfig.slots) {
8105
+ if (!slot.days.includes(dayOfWeek)) continue;
8106
+ const datetime = buildSlotDatetime(day, slot.time, timezone);
8107
+ const ms = normalizeDateTime(datetime);
8108
+ if (ms <= fromMs) continue;
8109
+ if (ms > upperMs) continue;
8110
+ dayCandidates.push({ datetime, ms });
8111
+ }
8112
+ dayCandidates.sort((a, b) => a.ms - b.ms);
8113
+ for (const candidate of dayCandidates) yield candidate;
8114
+ if (dayCandidates.length === 0) {
8115
+ const dayStartMs = normalizeDateTime(buildSlotDatetime(day, "00:00", timezone));
8116
+ if (dayStartMs > upperMs) break;
8117
+ }
8118
+ }
8119
+ }
8120
+ function passesIdeaSpacing(candidateMs, candidatePlatform, ideaRefs, samePlatformMs, crossPlatformMs) {
8121
+ for (const ref of ideaRefs) {
8122
+ const diff = Math.abs(candidateMs - ref.scheduledForMs);
8123
+ if (ref.platform === candidatePlatform && diff < samePlatformMs) return false;
8124
+ if (diff < crossPlatformMs) return false;
8125
+ }
8126
+ return true;
8127
+ }
8128
+ async function getIdeaReferences(ideaIds, bookedMap) {
8129
+ const sameIdeaPosts = await getScheduledItemsByIdeaIds(ideaIds);
7843
8130
  const lateSlotsByPostId = /* @__PURE__ */ new Map();
7844
8131
  const localSlotsByItemId = /* @__PURE__ */ new Map();
7845
- for (const slot of allBookedSlots) {
8132
+ for (const slot of bookedMap.values()) {
7846
8133
  if (slot.postId) {
7847
- const slots = lateSlotsByPostId.get(slot.postId) ?? [];
7848
- slots.push(slot);
7849
- lateSlotsByPostId.set(slot.postId, slots);
8134
+ const arr = lateSlotsByPostId.get(slot.postId) ?? [];
8135
+ arr.push(slot);
8136
+ lateSlotsByPostId.set(slot.postId, arr);
7850
8137
  }
7851
8138
  if (slot.itemId) {
7852
- const slots = localSlotsByItemId.get(slot.itemId) ?? [];
7853
- slots.push(slot);
7854
- localSlotsByItemId.set(slot.itemId, slots);
8139
+ const arr = localSlotsByItemId.get(slot.itemId) ?? [];
8140
+ arr.push(slot);
8141
+ localSlotsByItemId.set(slot.itemId, arr);
7855
8142
  }
7856
8143
  }
7857
- const references = [];
8144
+ const refs = [];
7858
8145
  const seen = /* @__PURE__ */ new Set();
7859
- const addReference = (platformName, scheduledFor) => {
8146
+ const addRef = (platform, scheduledFor) => {
7860
8147
  if (!scheduledFor) return;
7861
- const key = `${platformName}@${scheduledFor}`;
8148
+ const key = `${platform}@${scheduledFor}`;
7862
8149
  if (seen.has(key)) return;
7863
8150
  seen.add(key);
7864
- references.push({ platform: platformName, scheduledFor });
8151
+ refs.push({ platform, scheduledForMs: normalizeDateTime(scheduledFor) });
7865
8152
  };
7866
8153
  for (const item of sameIdeaPosts) {
7867
- addReference(item.metadata.platform, item.metadata.scheduledFor);
8154
+ addRef(item.metadata.platform, item.metadata.scheduledFor);
7868
8155
  if (item.metadata.latePostId) {
7869
8156
  for (const slot of lateSlotsByPostId.get(item.metadata.latePostId) ?? []) {
7870
- addReference(slot.platform, slot.scheduledFor);
8157
+ addRef(slot.platform, slot.scheduledFor);
7871
8158
  }
7872
8159
  }
7873
8160
  for (const slot of localSlotsByItemId.get(item.id) ?? []) {
7874
- addReference(slot.platform, slot.scheduledFor);
8161
+ addRef(slot.platform, slot.scheduledFor);
8162
+ }
8163
+ }
8164
+ return refs;
8165
+ }
8166
+ async function schedulePost(platformConfig, fromMs, isIdeaPost, label, ctx) {
8167
+ const indent = " ".repeat(ctx.depth);
8168
+ let checked = 0;
8169
+ let skippedBooked = 0;
8170
+ let skippedSpacing = 0;
8171
+ logger_default.debug(`${indent}[schedulePost] Looking for slot for ${label} (idea=${isIdeaPost}) from ${new Date(fromMs).toISOString()}`);
8172
+ for (const { datetime, ms } of generateTimeslots(platformConfig, ctx.timezone, fromMs)) {
8173
+ checked++;
8174
+ const booked = ctx.bookedMap.get(ms);
8175
+ if (!booked) {
8176
+ if (isIdeaPost && ctx.ideaRefs.length > 0 && !passesIdeaSpacing(ms, ctx.platform, ctx.ideaRefs, ctx.samePlatformMs, ctx.crossPlatformMs)) {
8177
+ skippedSpacing++;
8178
+ if (skippedSpacing <= 5 || skippedSpacing % 50 === 0) {
8179
+ logger_default.debug(`${indent}[schedulePost] \u23ED\uFE0F Slot ${datetime} too close to same-idea post \u2014 skipping`);
8180
+ }
8181
+ continue;
8182
+ }
8183
+ logger_default.debug(`${indent}[schedulePost] \u2705 Found empty slot: ${datetime} (checked ${checked} candidates, skipped ${skippedBooked} booked, ${skippedSpacing} spacing)`);
8184
+ return datetime;
7875
8185
  }
7876
- }
7877
- return references;
7878
- }
7879
- function createSpacingGuard(ideaReferences, samePlatformHours, crossPlatformHours) {
7880
- const samePlatformWindowMs = samePlatformHours * HOUR_MS;
7881
- const crossPlatformWindowMs = crossPlatformHours * HOUR_MS;
7882
- return (candidateMs, candidatePlatform) => {
7883
- for (const reference of ideaReferences) {
7884
- const referenceMs = normalizeDateTime(reference.scheduledFor);
7885
- const diff = Math.abs(candidateMs - referenceMs);
7886
- if (reference.platform === candidatePlatform && diff < samePlatformWindowMs) {
7887
- return false;
8186
+ if (isIdeaPost && ctx.displacementEnabled && !booked.ideaLinked && booked.source === "late" && booked.postId) {
8187
+ if (ctx.ideaRefs.length > 0 && !passesIdeaSpacing(ms, ctx.platform, ctx.ideaRefs, ctx.samePlatformMs, ctx.crossPlatformMs)) {
8188
+ skippedSpacing++;
8189
+ if (skippedSpacing <= 5 || skippedSpacing % 50 === 0) {
8190
+ logger_default.debug(`${indent}[schedulePost] \u23ED\uFE0F Slot ${datetime} too close to same-idea post \u2014 skipping (even though displaceable)`);
8191
+ }
8192
+ continue;
7888
8193
  }
7889
- if (diff < crossPlatformWindowMs) {
7890
- return false;
8194
+ logger_default.info(`${indent}[schedulePost] \u{1F504} Slot ${datetime} taken by non-idea post ${booked.postId} \u2014 displacing`);
8195
+ const newHome = await schedulePost(
8196
+ platformConfig,
8197
+ ms,
8198
+ false,
8199
+ `displaced:${booked.postId}`,
8200
+ { ...ctx, depth: ctx.depth + 1 }
8201
+ );
8202
+ if (newHome) {
8203
+ if (!ctx.dryRun) {
8204
+ try {
8205
+ await ctx.lateClient.schedulePost(booked.postId, newHome);
8206
+ } catch (err) {
8207
+ const msg = err instanceof Error ? err.message : String(err);
8208
+ logger_default.warn(`${indent}[schedulePost] \u26A0\uFE0F Failed to displace ${booked.postId} via Late API: ${msg} \u2014 skipping slot`);
8209
+ continue;
8210
+ }
8211
+ }
8212
+ logger_default.info(`${indent}[schedulePost] \u{1F4E6} Displaced ${booked.postId}: ${datetime} \u2192 ${newHome}`);
8213
+ ctx.bookedMap.delete(ms);
8214
+ const newMs = normalizeDateTime(newHome);
8215
+ ctx.bookedMap.set(newMs, { ...booked, scheduledFor: newHome });
8216
+ logger_default.debug(`${indent}[schedulePost] \u2705 Taking slot: ${datetime} (checked ${checked} candidates)`);
8217
+ return datetime;
7891
8218
  }
8219
+ logger_default.warn(`${indent}[schedulePost] \u26A0\uFE0F Could not displace ${booked.postId} \u2014 no empty slot found after ${datetime}`);
7892
8220
  }
7893
- return true;
7894
- };
7895
- }
7896
- function resolveSearchWindow(nowMs, options) {
7897
- const defaultWindowEndMs = nowMs + DEFAULT_IDEA_WINDOW_DAYS * DAY_MS;
7898
- const publishBy = options?.publishBy;
7899
- if (!publishBy) {
7900
- return {
7901
- emptyWindowEndMs: defaultWindowEndMs,
7902
- displacementWindowEndMs: defaultWindowEndMs
7903
- };
7904
- }
7905
- const publishByMs = normalizeDateTime(publishBy);
7906
- if (Number.isNaN(publishByMs)) {
7907
- logger_default.warn(`Invalid publishBy "${sanitizeLogValue(publishBy)}" provided; scheduling normally without urgency bias`);
7908
- return {};
7909
- }
7910
- const daysUntilPublishBy = (publishByMs - nowMs) / DAY_MS;
7911
- if (daysUntilPublishBy <= 0) {
7912
- logger_default.warn(`publishBy "${sanitizeLogValue(publishBy)}" has already passed; scheduling normally without urgency bias`);
7913
- return {};
7914
- }
7915
- if (daysUntilPublishBy < 3) {
7916
- logger_default.debug(`Urgent publishBy "${sanitizeLogValue(publishBy)}"; prioritizing earliest displaceable slot`);
7917
- }
7918
- return {
7919
- emptyWindowEndMs: publishByMs,
7920
- displacementWindowEndMs: daysUntilPublishBy < 7 ? Math.min(publishByMs, nowMs + 3 * DAY_MS) : publishByMs
7921
- };
7922
- }
7923
- function findEmptySlot({
7924
- platformConfig,
7925
- timezone,
7926
- bookedDatetimes,
7927
- platform,
7928
- searchFromMs,
7929
- includeSearchDay = false,
7930
- maxCandidateMs,
7931
- passesCandidate
7932
- }) {
7933
- if (maxCandidateMs !== void 0 && maxCandidateMs < searchFromMs) {
7934
- return null;
7935
- }
7936
- const baseDate = new Date(searchFromMs);
7937
- const initialOffset = includeSearchDay ? 0 : 1;
7938
- let maxDayOffset = MAX_LOOKAHEAD_DAYS;
7939
- if (maxCandidateMs !== void 0) {
7940
- maxDayOffset = Math.min(
7941
- MAX_LOOKAHEAD_DAYS,
7942
- Math.max(initialOffset, Math.ceil((maxCandidateMs - searchFromMs) / DAY_MS))
7943
- );
7944
- }
7945
- let startOffset = initialOffset;
7946
- while (startOffset <= maxDayOffset) {
7947
- const endOffset = Math.min(startOffset + CHUNK_DAYS - 1, maxDayOffset);
7948
- const candidates = [];
7949
- for (let dayOffset = startOffset; dayOffset <= endOffset; dayOffset++) {
7950
- const candidateDate = new Date(baseDate);
7951
- candidateDate.setDate(candidateDate.getDate() + dayOffset);
7952
- const dayOfWeek = getDayOfWeekInTimezone2(candidateDate, timezone);
7953
- if (platformConfig.avoidDays.includes(dayOfWeek)) continue;
7954
- for (const slot of platformConfig.slots) {
7955
- if (!slot.days.includes(dayOfWeek)) continue;
7956
- const candidate = buildSlotDatetime2(candidateDate, slot.time, timezone);
7957
- const candidateMs = normalizeDateTime(candidate);
7958
- if (candidateMs <= searchFromMs) continue;
7959
- if (maxCandidateMs !== void 0 && candidateMs > maxCandidateMs) continue;
7960
- if (bookedDatetimes.has(candidateMs)) continue;
7961
- if (passesCandidate && !passesCandidate(candidateMs, platform)) continue;
7962
- candidates.push(candidate);
7963
- }
7964
- }
7965
- candidates.sort((left, right) => normalizeDateTime(left) - normalizeDateTime(right));
7966
- if (candidates.length > 0) {
7967
- return candidates[0];
7968
- }
7969
- startOffset = endOffset + 1;
7970
- }
7971
- return null;
7972
- }
7973
- async function tryDisplacement({
7974
- bookedSlots,
7975
- platform,
7976
- platformConfig,
7977
- timezone,
7978
- bookedDatetimes,
7979
- options,
7980
- nowMs,
7981
- maxCandidateMs,
7982
- passesSpacing
7983
- }) {
7984
- const displacementConfig = getDisplacementConfig();
7985
- if (!displacementConfig.enabled || !options.ideaIds?.length) {
7986
- return null;
7987
- }
7988
- const candidateSlots = bookedSlots.filter((slot) => {
7989
- const slotMs = normalizeDateTime(slot.scheduledFor);
7990
- if (slotMs <= nowMs) return false;
7991
- if (maxCandidateMs !== void 0 && slotMs > maxCandidateMs) return false;
7992
- return true;
7993
- }).sort((left, right) => normalizeDateTime(left.scheduledFor) - normalizeDateTime(right.scheduledFor));
7994
- const lateClient = new LateApiClient();
7995
- const publishedItemCache = /* @__PURE__ */ new Map();
7996
- for (const slot of candidateSlots) {
7997
- if (slot.source !== "late" || !slot.postId) continue;
7998
- const candidateMs = normalizeDateTime(slot.scheduledFor);
7999
- if (passesSpacing && !passesSpacing(candidateMs, platform)) continue;
8000
- let publishedItem = publishedItemCache.get(slot.postId);
8001
- if (publishedItem === void 0) {
8002
- publishedItem = await getPublishedItemByLatePostId(slot.postId);
8003
- publishedItemCache.set(slot.postId, publishedItem);
8004
- }
8005
- if (!publishedItem) {
8221
+ if (booked.ideaLinked) {
8222
+ skippedBooked++;
8223
+ if (skippedBooked <= 5 || skippedBooked % 50 === 0) {
8224
+ logger_default.debug(`${indent}[schedulePost] \u23ED\uFE0F Slot ${datetime} taken by idea post ${booked.postId ?? booked.itemId} \u2014 skipping`);
8225
+ }
8006
8226
  continue;
8007
8227
  }
8008
- if (publishedItem.metadata.ideaIds?.length) {
8009
- continue;
8228
+ skippedBooked++;
8229
+ if (skippedBooked <= 5 || skippedBooked % 50 === 0) {
8230
+ logger_default.debug(`${indent}[schedulePost] \u23ED\uFE0F Slot ${datetime} taken (${booked.source}/${booked.postId ?? booked.itemId}) \u2014 skipping`);
8010
8231
  }
8011
- const displacedPlatformConfig = publishedItem?.metadata.clipType ? getPlatformSchedule(platform, publishedItem.metadata.clipType) ?? platformConfig : platformConfig;
8012
- const newSlot = findEmptySlot({
8013
- platformConfig: displacedPlatformConfig,
8014
- timezone,
8015
- bookedDatetimes,
8016
- platform,
8017
- searchFromMs: candidateMs,
8018
- includeSearchDay: true
8019
- });
8020
- if (!newSlot) continue;
8021
- await lateClient.schedulePost(slot.postId, newSlot);
8022
- logger_default.info(
8023
- `Displaced post ${sanitizeLogValue(slot.postId)} from ${sanitizeLogValue(slot.scheduledFor)} to ${sanitizeLogValue(newSlot)} for idea-linked content`
8024
- );
8025
- return {
8026
- slot: slot.scheduledFor,
8027
- displaced: {
8028
- postId: slot.postId,
8029
- originalSlot: slot.scheduledFor,
8030
- newSlot
8031
- }
8032
- };
8033
8232
  }
8233
+ logger_default.warn(`[schedulePost] \u274C No slot found for ${label} \u2014 checked ${checked} candidates, skipped ${skippedBooked} booked, ${skippedSpacing} spacing`);
8034
8234
  return null;
8035
8235
  }
8036
8236
  async function findNextSlot(platform, clipType, options) {
@@ -8040,58 +8240,46 @@ async function findNextSlot(platform, clipType, options) {
8040
8240
  logger_default.warn(`No schedule config found for platform "${sanitizeLogValue(platform)}"`);
8041
8241
  return null;
8042
8242
  }
8243
+ const { timezone } = config2;
8244
+ const nowMs = Date.now();
8043
8245
  const ideaIds = options?.ideaIds?.filter(Boolean) ?? [];
8044
8246
  const isIdeaAware = ideaIds.length > 0;
8045
- const nowMs = Date.now();
8046
- const { timezone } = config2;
8047
- const [allBookedSlots, sameIdeaPosts] = await Promise.all([
8048
- isIdeaAware ? buildBookedSlots() : Promise.resolve([]),
8049
- isIdeaAware ? getScheduledItemsByIdeaIds(ideaIds) : Promise.resolve([])
8050
- ]);
8051
- const bookedSlots = isIdeaAware ? allBookedSlots.filter((slot) => slot.platform === platform) : await buildBookedSlots(platform);
8052
- const bookedDatetimes = new Set(bookedSlots.map((slot) => normalizeDateTime(slot.scheduledFor)));
8053
- const spacingConfig = isIdeaAware ? getIdeaSpacingConfig() : null;
8054
- const spacingGuard = spacingConfig ? createSpacingGuard(
8055
- buildIdeaReferences(sameIdeaPosts, allBookedSlots),
8056
- spacingConfig.samePlatformHours,
8057
- spacingConfig.crossPlatformHours
8058
- ) : void 0;
8059
- const searchWindow = isIdeaAware ? resolveSearchWindow(nowMs, options) : {};
8060
- const emptySlot = findEmptySlot({
8061
- platformConfig,
8062
- timezone,
8063
- bookedDatetimes,
8064
- platform,
8065
- searchFromMs: nowMs,
8066
- maxCandidateMs: searchWindow.emptyWindowEndMs,
8067
- passesCandidate: spacingGuard
8068
- });
8069
- if (emptySlot) {
8070
- logger_default.debug(`Found available slot for ${sanitizeLogValue(platform)}: ${sanitizeLogValue(emptySlot)}`);
8071
- return emptySlot;
8072
- }
8247
+ const bookedMap = await buildBookedMap(platform);
8248
+ const ideaLinkedPostIds = await getIdeaLinkedLatePostIds();
8249
+ const label = `${platform}/${clipType ?? "default"}`;
8250
+ let ideaRefs = [];
8251
+ let samePlatformMs = 0;
8252
+ let crossPlatformMs = 0;
8073
8253
  if (isIdeaAware) {
8074
- const displaced = await tryDisplacement({
8075
- bookedSlots,
8076
- platform,
8077
- platformConfig,
8078
- timezone,
8079
- bookedDatetimes,
8080
- options: { ...options, ideaIds },
8081
- nowMs,
8082
- maxCandidateMs: searchWindow.displacementWindowEndMs,
8083
- passesSpacing: spacingGuard
8084
- });
8085
- if (displaced) {
8086
- return displaced.slot;
8087
- }
8254
+ const allBookedMap = await buildBookedMap();
8255
+ ideaRefs = await getIdeaReferences(ideaIds, allBookedMap);
8256
+ const spacingConfig = getIdeaSpacingConfig();
8257
+ samePlatformMs = spacingConfig.samePlatformHours * HOUR_MS;
8258
+ crossPlatformMs = spacingConfig.crossPlatformHours * HOUR_MS;
8259
+ }
8260
+ logger_default.info(`[findNextSlot] Scheduling ${label} (idea=${isIdeaAware}, booked=${bookedMap.size} slots, spacingRefs=${ideaRefs.length})`);
8261
+ const ctx = {
8262
+ timezone,
8263
+ bookedMap,
8264
+ ideaLinkedPostIds,
8265
+ lateClient: new LateApiClient(),
8266
+ displacementEnabled: getDisplacementConfig().enabled,
8267
+ dryRun: false,
8268
+ depth: 0,
8269
+ ideaRefs,
8270
+ samePlatformMs,
8271
+ crossPlatformMs,
8272
+ platform
8273
+ };
8274
+ const result = await schedulePost(platformConfig, nowMs, isIdeaAware, label, ctx);
8275
+ if (!result) {
8276
+ logger_default.warn(`[findNextSlot] No available slot for "${sanitizeLogValue(platform)}" within ${MAX_LOOKAHEAD_DAYS} days`);
8088
8277
  }
8089
- logger_default.warn(`No available slot found for "${sanitizeLogValue(platform)}" within ${MAX_LOOKAHEAD_DAYS} days`);
8090
- return null;
8278
+ return result;
8091
8279
  }
8092
8280
  async function getScheduleCalendar(startDate, endDate) {
8093
- const slots = await buildBookedSlots();
8094
- let filtered = slots.filter((slot) => slot.source === "local" || slot.status === "scheduled").map((slot) => ({
8281
+ const bookedMap = await buildBookedMap();
8282
+ let filtered = [...bookedMap.values()].filter((slot) => slot.source === "local" || slot.status === "scheduled").map((slot) => ({
8095
8283
  platform: slot.platform,
8096
8284
  scheduledFor: slot.scheduledFor,
8097
8285
  source: slot.source,
@@ -8110,6 +8298,206 @@ async function getScheduleCalendar(startDate, endDate) {
8110
8298
  return filtered;
8111
8299
  }
8112
8300
 
8301
+ // src/L3-services/scheduler/realign.ts
8302
+ var PLATFORM_ALIASES2 = { twitter: "x" };
8303
+ function normalizeSchedulePlatform(platform) {
8304
+ return PLATFORM_ALIASES2[platform] ?? platform;
8305
+ }
8306
+ function normalizeContent(content) {
8307
+ return content.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 200);
8308
+ }
8309
+ async function buildClipTypeMaps() {
8310
+ const published = await getPublishedItems();
8311
+ const byLatePostId = /* @__PURE__ */ new Map();
8312
+ const byContent = /* @__PURE__ */ new Map();
8313
+ for (const item of published) {
8314
+ if (item.metadata.latePostId) {
8315
+ byLatePostId.set(item.metadata.latePostId, item.metadata.clipType);
8316
+ }
8317
+ if (item.postContent) {
8318
+ const contentKey = `${item.metadata.platform}::${normalizeContent(item.postContent)}`;
8319
+ byContent.set(contentKey, item.metadata.clipType);
8320
+ }
8321
+ }
8322
+ logger_default.debug(`Built clipType maps: ${byLatePostId.size} by latePostId, ${byContent.size} by content`);
8323
+ return { byLatePostId, byContent };
8324
+ }
8325
+ async function fetchAllPosts(client, statuses, platform) {
8326
+ const allPosts = [];
8327
+ for (const status of statuses) {
8328
+ const posts = await client.listPosts({ status, platform });
8329
+ allPosts.push(...posts);
8330
+ logger_default.info(`Fetched ${posts.length} ${status} post(s)${platform ? ` for ${platform}` : ""}`);
8331
+ }
8332
+ return allPosts;
8333
+ }
8334
+ function isOnValidSlot(iso, schedule, timezone) {
8335
+ if (schedule.slots.length === 0) return false;
8336
+ const date = new Date(iso);
8337
+ const dayOfWeek = getDayOfWeekInTimezone(date, timezone);
8338
+ if (schedule.avoidDays.includes(dayOfWeek)) return false;
8339
+ const timeFormatter = new Intl.DateTimeFormat("en-US", {
8340
+ timeZone: timezone,
8341
+ hour: "2-digit",
8342
+ minute: "2-digit",
8343
+ hour12: false
8344
+ });
8345
+ const timeParts = timeFormatter.formatToParts(date);
8346
+ const hour = timeParts.find((p) => p.type === "hour")?.value ?? "00";
8347
+ const minute = timeParts.find((p) => p.type === "minute")?.value ?? "00";
8348
+ const timeKey = `${hour}:${minute}`;
8349
+ return schedule.slots.some((slot) => slot.time === timeKey && slot.days.includes(dayOfWeek));
8350
+ }
8351
+ async function buildRealignPlan(options = {}) {
8352
+ const config2 = await loadScheduleConfig();
8353
+ const { timezone } = config2;
8354
+ const client = new LateApiClient();
8355
+ const statuses = ["scheduled", "draft", "cancelled", "failed"];
8356
+ const allPosts = await fetchAllPosts(client, statuses, options.platform);
8357
+ if (allPosts.length === 0) {
8358
+ return { posts: [], toCancel: [], skipped: 0, unmatched: 0, totalFetched: 0 };
8359
+ }
8360
+ const { byLatePostId, byContent } = options.clipTypeMaps ?? await buildClipTypeMaps();
8361
+ let unmatched = 0;
8362
+ const tagged = [];
8363
+ for (const post of allPosts) {
8364
+ const platform = post.platforms[0]?.platform;
8365
+ if (!platform) continue;
8366
+ let clipType = byLatePostId.get(post._id) ?? null;
8367
+ if (!clipType && post.content) {
8368
+ const contentKey = `${platform}::${normalizeContent(post.content)}`;
8369
+ clipType = byContent.get(contentKey) ?? null;
8370
+ }
8371
+ if (!clipType) {
8372
+ clipType = "short";
8373
+ unmatched++;
8374
+ }
8375
+ tagged.push({ post, platform, clipType });
8376
+ }
8377
+ const bookedMap = await buildBookedMap();
8378
+ const ctx = {
8379
+ timezone,
8380
+ bookedMap,
8381
+ ideaLinkedPostIds: /* @__PURE__ */ new Set(),
8382
+ lateClient: client,
8383
+ displacementEnabled: getDisplacementConfig().enabled,
8384
+ dryRun: true,
8385
+ depth: 0,
8386
+ ideaRefs: [],
8387
+ samePlatformMs: 0,
8388
+ crossPlatformMs: 0,
8389
+ platform: ""
8390
+ };
8391
+ for (const [, slot] of bookedMap) {
8392
+ if (slot.ideaLinked && slot.postId) {
8393
+ ctx.ideaLinkedPostIds.add(slot.postId);
8394
+ }
8395
+ }
8396
+ const result = [];
8397
+ const toCancel = [];
8398
+ let skipped = 0;
8399
+ tagged.sort((a, b) => {
8400
+ const aIdea = ctx.ideaLinkedPostIds.has(a.post._id) ? 0 : 1;
8401
+ const bIdea = ctx.ideaLinkedPostIds.has(b.post._id) ? 0 : 1;
8402
+ return aIdea - bIdea;
8403
+ });
8404
+ const nowMs = Date.now();
8405
+ for (const { post, platform, clipType } of tagged) {
8406
+ const schedulePlatform = normalizeSchedulePlatform(platform);
8407
+ const platformConfig = getPlatformSchedule(schedulePlatform, clipType);
8408
+ if (!platformConfig || platformConfig.slots.length === 0) {
8409
+ if (post.status !== "cancelled") {
8410
+ toCancel.push({ post, platform, clipType, reason: `No schedule slots for ${schedulePlatform}/${clipType}` });
8411
+ }
8412
+ continue;
8413
+ }
8414
+ if (post.scheduledFor && post.status === "scheduled" && isOnValidSlot(post.scheduledFor, platformConfig, timezone)) {
8415
+ skipped++;
8416
+ continue;
8417
+ }
8418
+ if (post.scheduledFor) {
8419
+ const currentMs2 = new Date(post.scheduledFor).getTime();
8420
+ const currentBooked = bookedMap.get(currentMs2);
8421
+ if (currentBooked?.postId === post._id) {
8422
+ bookedMap.delete(currentMs2);
8423
+ }
8424
+ }
8425
+ const isIdea = ctx.ideaLinkedPostIds.has(post._id);
8426
+ const label = `${schedulePlatform}/${clipType}:${post._id.slice(-6)}`;
8427
+ const newSlot = await schedulePost(platformConfig, nowMs, isIdea, label, ctx);
8428
+ if (!newSlot) {
8429
+ if (post.status !== "cancelled") {
8430
+ toCancel.push({ post, platform, clipType, reason: `No available slot for ${schedulePlatform}/${clipType}` });
8431
+ }
8432
+ continue;
8433
+ }
8434
+ const newMs = new Date(newSlot).getTime();
8435
+ ctx.bookedMap.set(newMs, {
8436
+ scheduledFor: newSlot,
8437
+ source: "late",
8438
+ postId: post._id,
8439
+ platform: schedulePlatform,
8440
+ ideaLinked: isIdea
8441
+ });
8442
+ const currentMs = post.scheduledFor ? new Date(post.scheduledFor).getTime() : 0;
8443
+ if (currentMs === newMs && post.status === "scheduled") {
8444
+ skipped++;
8445
+ continue;
8446
+ }
8447
+ result.push({
8448
+ post,
8449
+ platform,
8450
+ clipType,
8451
+ oldScheduledFor: post.scheduledFor ?? null,
8452
+ newScheduledFor: newSlot
8453
+ });
8454
+ }
8455
+ result.sort((a, b) => new Date(a.newScheduledFor).getTime() - new Date(b.newScheduledFor).getTime());
8456
+ return { posts: result, toCancel, skipped, unmatched, totalFetched: allPosts.length };
8457
+ }
8458
+ async function executeRealignPlan(plan, onProgress) {
8459
+ const client = new LateApiClient();
8460
+ let updated = 0;
8461
+ let cancelled = 0;
8462
+ let failed = 0;
8463
+ const errors = [];
8464
+ const totalOps = plan.toCancel.length + plan.posts.length;
8465
+ let completed = 0;
8466
+ for (const entry of plan.toCancel) {
8467
+ completed++;
8468
+ try {
8469
+ await client.updatePost(entry.post._id, { status: "cancelled" });
8470
+ cancelled++;
8471
+ const preview = entry.post.content.slice(0, 40).replace(/\n/g, " ");
8472
+ logger_default.info(`[${completed}/${totalOps}] \u{1F6AB} Cancelled ${entry.platform}/${entry.clipType}: "${preview}..."`);
8473
+ onProgress?.(completed, totalOps, "cancelling");
8474
+ await new Promise((r) => setTimeout(r, 300));
8475
+ } catch (err) {
8476
+ const msg = err instanceof Error ? err.message : String(err);
8477
+ errors.push({ postId: entry.post._id, error: msg });
8478
+ failed++;
8479
+ logger_default.error(`[${completed}/${totalOps}] \u274C Failed to cancel ${entry.post._id}: ${msg}`);
8480
+ }
8481
+ }
8482
+ for (const entry of plan.posts) {
8483
+ completed++;
8484
+ try {
8485
+ await client.schedulePost(entry.post._id, entry.newScheduledFor);
8486
+ updated++;
8487
+ const preview = entry.post.content.slice(0, 40).replace(/\n/g, " ");
8488
+ logger_default.info(`[${completed}/${totalOps}] \u2705 ${entry.platform}/${entry.clipType}: "${preview}..." \u2192 ${entry.newScheduledFor}`);
8489
+ onProgress?.(completed, totalOps, "updating");
8490
+ await new Promise((r) => setTimeout(r, 300));
8491
+ } catch (err) {
8492
+ const msg = err instanceof Error ? err.message : String(err);
8493
+ errors.push({ postId: entry.post._id, error: msg });
8494
+ failed++;
8495
+ logger_default.error(`[${completed}/${totalOps}] \u274C Failed to update ${entry.post._id}: ${msg}`);
8496
+ }
8497
+ }
8498
+ return { updated, cancelled, failed, errors };
8499
+ }
8500
+
8113
8501
  // src/L2-clients/ffmpeg/audioExtraction.ts
8114
8502
  init_fileSystem();
8115
8503
  init_paths();
@@ -9271,6 +9659,8 @@ function transcodeToMp4(inputPath, outputPath) {
9271
9659
  createFFmpeg(inputPath).outputOptions([
9272
9660
  "-c:v",
9273
9661
  "libx264",
9662
+ "-pix_fmt",
9663
+ "yuv420p",
9274
9664
  "-preset",
9275
9665
  "ultrafast",
9276
9666
  "-crf",
@@ -9574,32 +9964,6 @@ async function markFailed(slug, error) {
9574
9964
  logger_default.info(`[ProcessingState] Marked failed: ${slug} \u2014 ${error}`);
9575
9965
  }
9576
9966
 
9577
- // src/L3-services/gitOperations/gitOperations.ts
9578
- init_environment();
9579
- init_configLogger();
9580
- async function commitAndPush(videoSlug, message) {
9581
- const { REPO_ROOT } = getConfig();
9582
- const commitMessage = message || `Auto-processed video: ${videoSlug}`;
9583
- try {
9584
- logger_default.info(`Staging all changes in ${REPO_ROOT}`);
9585
- execCommandSync("git add -A", { cwd: REPO_ROOT, stdio: "pipe" });
9586
- logger_default.info(`Committing: ${commitMessage}`);
9587
- execCommandSync(`git commit -m "${commitMessage}"`, { cwd: REPO_ROOT, stdio: "pipe" });
9588
- const branch = execCommandSync("git rev-parse --abbrev-ref HEAD", { cwd: REPO_ROOT, stdio: "pipe" });
9589
- logger_default.info(`Pushing to origin ${branch}`);
9590
- execCommandSync(`git push origin ${branch}`, { cwd: REPO_ROOT, stdio: "pipe" });
9591
- logger_default.info("Git commit and push completed successfully");
9592
- } catch (error) {
9593
- const msg = error instanceof Error ? error.message : String(error);
9594
- if (msg.includes("nothing to commit")) {
9595
- logger_default.info("Nothing to commit, working tree clean");
9596
- return;
9597
- }
9598
- logger_default.error(`Git operation failed: ${msg}`);
9599
- throw error;
9600
- }
9601
- }
9602
-
9603
9967
  // src/L3-services/queueBuilder/queueBuilder.ts
9604
9968
  init_fileSystem();
9605
9969
  init_paths();
@@ -9640,6 +10004,9 @@ function platformAcceptsMedia(platform, clipType) {
9640
10004
  return getMediaRule(platform, clipType) !== null;
9641
10005
  }
9642
10006
 
10007
+ // src/L3-services/queueBuilder/queueBuilder.ts
10008
+ init_postStore();
10009
+
9643
10010
  // src/L1-infra/image/image.ts
9644
10011
  import { default as default7 } from "sharp";
9645
10012
 
@@ -9904,9 +10271,6 @@ function markCompleted2(...args) {
9904
10271
  function markFailed2(...args) {
9905
10272
  return markFailed(...args);
9906
10273
  }
9907
- function commitAndPush2(...args) {
9908
- return commitAndPush(...args);
9909
- }
9910
10274
  function buildPublishQueue2(...args) {
9911
10275
  return buildPublishQueue(...args);
9912
10276
  }
@@ -9960,6 +10324,7 @@ var BaseAgent = class _BaseAgent {
9960
10324
  for (let attempt = 1; attempt <= _BaseAgent.MAX_RETRIES; attempt++) {
9961
10325
  try {
9962
10326
  if (!this.session) {
10327
+ logger_default.info(`[${this.agentName}] Creating LLM session (provider=${this.provider.name})\u2026`);
9963
10328
  this.session = await this.provider.createSession({
9964
10329
  systemPrompt: this.systemPrompt,
9965
10330
  tools: this.getTools(),
@@ -9970,6 +10335,7 @@ var BaseAgent = class _BaseAgent {
9970
10335
  onUserInputRequest: this.getUserInputHandler()
9971
10336
  });
9972
10337
  this.setupEventHandlers(this.session);
10338
+ logger_default.info(`[${this.agentName}] LLM session ready`);
9973
10339
  }
9974
10340
  logger_default.info(`[${this.agentName}] Sending message (attempt ${attempt}/${_BaseAgent.MAX_RETRIES}): ${userMessage.substring(0, 80)}\u2026`);
9975
10341
  costTracker.setAgent(this.agentName);
@@ -10007,11 +10373,12 @@ var BaseAgent = class _BaseAgent {
10007
10373
  }
10008
10374
  /** Check if an error message indicates a transient/retryable failure. */
10009
10375
  static isRetryableError(message) {
10010
- const retryablePatterns = [
10376
+ const lower = message.toLowerCase();
10377
+ return [
10011
10378
  "missing finish_reason",
10012
- "ECONNRESET",
10013
- "ETIMEDOUT",
10014
- "ECONNREFUSED",
10379
+ "econnreset",
10380
+ "etimedout",
10381
+ "econnrefused",
10015
10382
  "socket hang up",
10016
10383
  "network error",
10017
10384
  "rate limit",
@@ -10021,10 +10388,9 @@ var BaseAgent = class _BaseAgent {
10021
10388
  "503",
10022
10389
  "504",
10023
10390
  "stream ended",
10024
- "aborted"
10025
- ];
10026
- const lower = message.toLowerCase();
10027
- return retryablePatterns.some((p) => lower.includes(p.toLowerCase()));
10391
+ "aborted",
10392
+ "cli server exited"
10393
+ ].some((p) => lower.includes(p));
10028
10394
  }
10029
10395
  /** Wire up session event listeners for logging. Override for custom display. */
10030
10396
  setupEventHandlers(session) {
@@ -10328,7 +10694,7 @@ function buildSystemPrompt(brand, existingIdeas, seedTopics, count, ideaRepo) {
10328
10694
  }
10329
10695
  return promptSections.join("\n");
10330
10696
  }
10331
- function buildUserMessage(count, seedTopics, hasMcpServers) {
10697
+ function buildUserMessage(count, seedTopics, hasMcpServers, userPrompt) {
10332
10698
  const focusText = seedTopics.length > 0 ? `Focus areas: ${seedTopics.join(", ")}` : "Focus areas: choose the strongest timely opportunities from the creator context and current trends.";
10333
10699
  const steps = [
10334
10700
  "1. Call get_brand_context to load the creator profile.",
@@ -10352,13 +10718,15 @@ function buildUserMessage(count, seedTopics, hasMcpServers) {
10352
10718
  "5. Call finalize_ideas when done."
10353
10719
  );
10354
10720
  }
10355
- return [
10721
+ const sections = [
10356
10722
  `Generate ${count} new content ideas.`,
10357
- focusText,
10358
- "",
10359
- "Follow this exact workflow:",
10360
- ...steps
10361
- ].join("\n");
10723
+ focusText
10724
+ ];
10725
+ if (userPrompt) {
10726
+ sections.push("", `## User Prompt`, userPrompt);
10727
+ }
10728
+ sections.push("", "Follow this exact workflow:", ...steps);
10729
+ return sections.join("\n");
10362
10730
  }
10363
10731
  async function loadBrandContext(brandPath) {
10364
10732
  if (!brandPath) {
@@ -10875,7 +11243,7 @@ async function generateIdeas(options = {}) {
10875
11243
  });
10876
11244
  try {
10877
11245
  const hasMcpServers = !!(config2.EXA_API_KEY || config2.YOUTUBE_API_KEY || config2.PERPLEXITY_API_KEY);
10878
- const userMessage = buildUserMessage(count, seedTopics, hasMcpServers);
11246
+ const userMessage = buildUserMessage(count, seedTopics, hasMcpServers, options.prompt);
10879
11247
  await agent.run(userMessage);
10880
11248
  const ideas = agent.getGeneratedIdeas();
10881
11249
  if (!agent.isFinalized()) {
@@ -10923,32 +11291,6 @@ init_fileSystem();
10923
11291
  init_configLogger();
10924
11292
  init_environment();
10925
11293
 
10926
- // src/L1-infra/progress/progressEmitter.ts
10927
- var ProgressEmitter = class {
10928
- enabled = false;
10929
- /** Turn on progress event output to stderr. */
10930
- enable() {
10931
- this.enabled = true;
10932
- }
10933
- /** Turn off progress event output. */
10934
- disable() {
10935
- this.enabled = false;
10936
- }
10937
- /** Whether the emitter is currently active. */
10938
- isEnabled() {
10939
- return this.enabled;
10940
- }
10941
- /**
10942
- * Write a progress event as a single JSON line to stderr.
10943
- * No-op when the emitter is disabled.
10944
- */
10945
- emit(event) {
10946
- if (!this.enabled) return;
10947
- process.stderr.write(JSON.stringify(event) + "\n");
10948
- }
10949
- };
10950
- var progressEmitter = new ProgressEmitter();
10951
-
10952
11294
  // src/L5-assets/Asset.ts
10953
11295
  init_fileSystem();
10954
11296
  var Asset = class {
@@ -15860,12 +16202,6 @@ var MainVideoAsset = class _MainVideoAsset extends VideoAsset {
15860
16202
  async buildQueue(shorts, mediumClips, socialPosts, captionedVideoPath) {
15861
16203
  await this.buildPublishQueueData(shorts, mediumClips, socialPosts, captionedVideoPath);
15862
16204
  }
15863
- /**
15864
- * Commit and push all generated assets via git.
15865
- */
15866
- async commitAndPushChanges(message) {
15867
- return commitAndPush2(this.slug, message);
15868
- }
15869
16205
  };
15870
16206
 
15871
16207
  // src/L6-pipeline/pipeline.ts
@@ -16071,11 +16407,6 @@ async function processVideo(videoPath, ideas) {
16071
16407
  skipStage("queue-build" /* QueueBuild */, "NO_SOCIAL_POSTS");
16072
16408
  }
16073
16409
  const blogPost = await trackStage("blog" /* Blog */, () => asset.getBlog());
16074
- if (!cfg.SKIP_GIT) {
16075
- await trackStage("git-push" /* GitPush */, () => asset.commitAndPushChanges());
16076
- } else {
16077
- skipStage("git-push" /* GitPush */, "SKIP_GIT");
16078
- }
16079
16410
  const totalDuration = Date.now() - pipelineStart;
16080
16411
  const report = costTracker3.getReport();
16081
16412
  if (report.records.length > 0) {
@@ -16198,7 +16529,8 @@ var defaultKeys = [
16198
16529
  "brandPath",
16199
16530
  "ideasRepo",
16200
16531
  "lateProfileId",
16201
- "geminiModel"
16532
+ "geminiModel",
16533
+ "scheduleConfig"
16202
16534
  ];
16203
16535
  var configKeyMap = {
16204
16536
  "openai-key": { section: "credentials", key: "openaiApiKey" },
@@ -16216,7 +16548,8 @@ var configKeyMap = {
16216
16548
  "brand-path": { section: "defaults", key: "brandPath" },
16217
16549
  "ideas-repo": { section: "defaults", key: "ideasRepo" },
16218
16550
  "late-profile-id": { section: "defaults", key: "lateProfileId" },
16219
- "gemini-model": { section: "defaults", key: "geminiModel" }
16551
+ "gemini-model": { section: "defaults", key: "geminiModel" },
16552
+ "schedule-config": { section: "defaults", key: "scheduleConfig" }
16220
16553
  };
16221
16554
  var providerDefaults = {
16222
16555
  copilot: "Claude Opus 4.6",
@@ -16252,20 +16585,11 @@ var platformVariantMap = {
16252
16585
  youtube: "youtube",
16253
16586
  "youtube-shorts": "youtube-shorts"
16254
16587
  };
16255
- function applySdkEnvironment(sdkConfig) {
16256
- if (!sdkConfig) {
16257
- return;
16258
- }
16259
- if (sdkConfig.anthropicApiKey) process.env.ANTHROPIC_API_KEY = sdkConfig.anthropicApiKey;
16260
- if (sdkConfig.geminiApiKey) process.env.GEMINI_API_KEY = sdkConfig.geminiApiKey;
16261
- if (sdkConfig.llmProvider) process.env.LLM_PROVIDER = sdkConfig.llmProvider;
16262
- if (sdkConfig.llmModel) process.env.LLM_MODEL = sdkConfig.llmModel;
16263
- if (sdkConfig.geminiModel) process.env.GEMINI_MODEL = sdkConfig.geminiModel;
16264
- if (sdkConfig.repoRoot) process.env.REPO_ROOT = sdkConfig.repoRoot;
16265
- }
16266
16588
  function mapSdkConfigToCliOptions(sdkConfig) {
16267
16589
  return {
16268
16590
  openaiKey: sdkConfig?.openaiApiKey,
16591
+ anthropicKey: sdkConfig?.anthropicApiKey,
16592
+ geminiKey: sdkConfig?.geminiApiKey,
16269
16593
  exaKey: sdkConfig?.exaApiKey,
16270
16594
  youtubeKey: sdkConfig?.youtubeApiKey,
16271
16595
  perplexityKey: sdkConfig?.perplexityApiKey,
@@ -16276,12 +16600,15 @@ function mapSdkConfigToCliOptions(sdkConfig) {
16276
16600
  lateApiKey: sdkConfig?.lateApiKey,
16277
16601
  lateProfileId: sdkConfig?.lateProfileId,
16278
16602
  ideasRepo: sdkConfig?.ideasRepo,
16279
- githubToken: sdkConfig?.githubToken
16603
+ githubToken: sdkConfig?.githubToken,
16604
+ llmProvider: sdkConfig?.llmProvider,
16605
+ llmModel: sdkConfig?.llmModel,
16606
+ geminiModel: sdkConfig?.geminiModel,
16607
+ repoRoot: sdkConfig?.repoRoot
16280
16608
  };
16281
16609
  }
16282
16610
  function mapProcessOptionsToCliOverrides(options) {
16283
16611
  const overrides = {};
16284
- if (options?.skipGit !== void 0) overrides.git = !options.skipGit;
16285
16612
  if (options?.skipSilenceRemoval !== void 0) overrides.silenceRemoval = !options.skipSilenceRemoval;
16286
16613
  if (options?.skipShorts !== void 0) overrides.shorts = !options.skipShorts;
16287
16614
  if (options?.skipMediumClips !== void 0) overrides.mediumClips = !options.skipMediumClips;
@@ -16346,10 +16673,10 @@ function applyPersistedConfigToRuntime(target, value, currentCliOptions) {
16346
16673
  currentCliOptions.githubToken = value;
16347
16674
  break;
16348
16675
  case "credentials.anthropicApiKey":
16349
- process.env.ANTHROPIC_API_KEY = value;
16676
+ currentCliOptions.anthropicKey = value;
16350
16677
  break;
16351
16678
  case "credentials.geminiApiKey":
16352
- process.env.GEMINI_API_KEY = value;
16679
+ currentCliOptions.geminiKey = value;
16353
16680
  break;
16354
16681
  case "defaults.outputDir":
16355
16682
  currentCliOptions.outputDir = value;
@@ -16367,13 +16694,13 @@ function applyPersistedConfigToRuntime(target, value, currentCliOptions) {
16367
16694
  currentCliOptions.lateProfileId = value;
16368
16695
  break;
16369
16696
  case "defaults.llmProvider":
16370
- process.env.LLM_PROVIDER = value;
16697
+ currentCliOptions.llmProvider = value;
16371
16698
  break;
16372
16699
  case "defaults.llmModel":
16373
- process.env.LLM_MODEL = value;
16700
+ currentCliOptions.llmModel = value;
16374
16701
  break;
16375
16702
  case "defaults.geminiModel":
16376
- process.env.GEMINI_MODEL = value;
16703
+ currentCliOptions.geminiModel = value;
16377
16704
  break;
16378
16705
  default:
16379
16706
  break;
@@ -16386,10 +16713,6 @@ function applyRuntimeOnlyOverride(rawKey, value, currentCliOptions) {
16386
16713
  case "VERBOSE":
16387
16714
  currentCliOptions.verbose = Boolean(value);
16388
16715
  return true;
16389
- case "skipGit":
16390
- case "SKIP_GIT":
16391
- currentCliOptions.git = !Boolean(value);
16392
- return true;
16393
16716
  case "skipSilenceRemoval":
16394
16717
  case "SKIP_SILENCE_REMOVAL":
16395
16718
  currentCliOptions.silenceRemoval = !Boolean(value);
@@ -16420,15 +16743,15 @@ function applyRuntimeOnlyOverride(rawKey, value, currentCliOptions) {
16420
16743
  return true;
16421
16744
  case "repoRoot":
16422
16745
  case "REPO_ROOT":
16423
- process.env.REPO_ROOT = String(value);
16746
+ currentCliOptions.repoRoot = String(value);
16424
16747
  return true;
16425
16748
  case "ffmpegPath":
16426
16749
  case "FFMPEG_PATH":
16427
- process.env.FFMPEG_PATH = String(value);
16750
+ currentCliOptions.ffmpegPath = String(value);
16428
16751
  return true;
16429
16752
  case "ffprobePath":
16430
16753
  case "FFPROBE_PATH":
16431
- process.env.FFPROBE_PATH = String(value);
16754
+ currentCliOptions.ffprobePath = String(value);
16432
16755
  return true;
16433
16756
  default:
16434
16757
  return false;
@@ -16587,7 +16910,6 @@ async function buildSocialPosts(context, platforms) {
16587
16910
  }));
16588
16911
  }
16589
16912
  function createVidPipe(sdkConfig) {
16590
- applySdkEnvironment(sdkConfig);
16591
16913
  let currentCliOptions = mapSdkConfigToCliOptions(sdkConfig);
16592
16914
  initConfig(currentCliOptions);
16593
16915
  function refreshConfig() {
@@ -16658,22 +16980,6 @@ function createVidPipe(sdkConfig) {
16658
16980
  status: buildDiagnosticStatus(false, Boolean(config2.EXA_API_KEY)),
16659
16981
  message: config2.EXA_API_KEY ? "EXA_API_KEY is configured" : "EXA_API_KEY is not configured (optional)"
16660
16982
  });
16661
- try {
16662
- const gitResult = spawnCommand("git", ["--version"], { timeout: 1e4 });
16663
- const passed = gitResult.status === 0 && typeof gitResult.stdout === "string" && gitResult.stdout.length > 0;
16664
- checks.push({
16665
- name: "git",
16666
- status: buildDiagnosticStatus(false, passed),
16667
- message: passed ? `Git ${parseVersionFromOutput(gitResult.stdout)} detected` : "Git is not available (optional for git-push stage)"
16668
- });
16669
- } catch (error) {
16670
- checks.push({
16671
- name: "git",
16672
- status: "warn",
16673
- message: "Git is not available (optional for git-push stage)",
16674
- details: error instanceof Error ? error.message : String(error)
16675
- });
16676
- }
16677
16983
  const watchFolder = config2.WATCH_FOLDER || join(process.cwd(), "watch");
16678
16984
  checks.push({
16679
16985
  name: "watch-folder",
@@ -16758,13 +17064,19 @@ function createVidPipe(sdkConfig) {
16758
17064
  const cliOverrides = mapProcessOptionsToCliOverrides(options);
16759
17065
  const ideaIds = options?.ideas?.map((ideaId) => String(ideaId));
16760
17066
  const ideas = ideaIds && ideaIds.length > 0 ? await getIdeasByIds(ideaIds) : void 0;
16761
- return await withTemporaryCliOverrides(cliOverrides, async () => {
16762
- const result = await processVideoSafe(videoPath, ideas);
16763
- if (!result) {
16764
- throw new Error(`VidPipe pipeline failed for "${videoPath}" with an uncaught error`);
16765
- }
16766
- return result;
16767
- });
17067
+ const listener = options?.onProgress;
17068
+ if (listener) progressEmitter.addListener(listener);
17069
+ try {
17070
+ return await withTemporaryCliOverrides(cliOverrides, async () => {
17071
+ const result = await processVideoSafe(videoPath, ideas);
17072
+ if (!result) {
17073
+ throw new Error(`VidPipe pipeline failed for "${videoPath}" with an uncaught error`);
17074
+ }
17075
+ return result;
17076
+ });
17077
+ } finally {
17078
+ if (listener) progressEmitter.removeListener(listener);
17079
+ }
16768
17080
  },
16769
17081
  async ideate(options) {
16770
17082
  return await generateIdeas3({