@hyperdrive.bot/paseo-server 0.3.39 → 0.3.41
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/README.md +3 -3
- package/dist/server/server/agent/agent-manager.js +15 -0
- package/dist/server/server/agent/agent-projections.js +3 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +23 -0
- package/dist/server/server/agent/agent-storage.d.ts +2 -1
- package/dist/server/server/agent/agent-storage.js +4 -0
- package/dist/server/server/agent/mcp-shared.js +5 -2
- package/dist/server/server/agent/providers/claude/agent.d.ts +34 -0
- package/dist/server/server/agent/providers/claude/agent.js +74 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +7 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.js +6 -0
- package/dist/server/server/agent/providers/claude/tool-allowlist-guard.d.ts +41 -0
- package/dist/server/server/agent/providers/claude/tool-allowlist-guard.js +93 -0
- package/dist/server/server/agent/providers/claude/tool-allowlist.d.ts +68 -0
- package/dist/server/server/agent/providers/claude/tool-allowlist.js +133 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +33 -1
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +110 -5
- package/dist/server/server/agent/providers/claude/transport/pty.d.ts +32 -0
- package/dist/server/server/agent/providers/claude/transport/pty.js +69 -5
- package/dist/server/server/agent/providers/claude/transport/sdk.d.ts +2 -0
- package/dist/server/server/agent/providers/claude/transport/sdk.js +4 -0
- package/dist/server/server/agent/providers/claude/transport/types.d.ts +8 -0
- package/dist/server/server/agent/tools/paseo-tools.d.ts +19 -0
- package/dist/server/server/agent/tools/paseo-tools.js +213 -38
- package/dist/server/server/agent/tools/read-only-surface.d.ts +1 -0
- package/dist/server/server/agent/tools/read-only-surface.js +1 -0
- package/dist/server/server/persistence-hooks.js +2 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +13 -1
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +27 -3
- package/dist/server/server/session.js +6 -2
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js → index-cb251ddad56c08c3021036af3a43fc0f.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/index-cb251ddad56c08c3021036af3a43fc0f.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-cb251ddad56c08c3021036af3a43fc0f.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js.map.br → index-cb251ddad56c08c3021036af3a43fc0f.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js.map.gz → index-cb251ddad56c08c3021036af3a43fc0f.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-46b675f5daddb88c514f0eee26695f71.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-46b675f5daddb88c514f0eee26695f71.js.gz +0 -0
|
@@ -141,6 +141,51 @@ function resolveScheduleUpdateCadence(input) {
|
|
|
141
141
|
}
|
|
142
142
|
return undefined;
|
|
143
143
|
}
|
|
144
|
+
// A duration ("90m", "2h30m", "45" seconds) rather than a timestamp. Checked
|
|
145
|
+
// first so `at` can accept both without Date parsing guessing at bare numbers.
|
|
146
|
+
const SCHEDULE_DURATION_ONLY_PATTERN = /^(?:\d+|(?:\d+[smhd])+)$/;
|
|
147
|
+
function resolveScheduleAtInstant(at, now) {
|
|
148
|
+
if (SCHEDULE_DURATION_ONLY_PATTERN.test(at)) {
|
|
149
|
+
return new Date(now.getTime() + parseDurationString(at));
|
|
150
|
+
}
|
|
151
|
+
const parsed = new Date(at);
|
|
152
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
153
|
+
throw new Error(`Invalid at value: ${at}. Use an ISO 8601 instant (2026-08-21T21:30:00-03:00) or a duration from now (90m).`);
|
|
154
|
+
}
|
|
155
|
+
return parsed;
|
|
156
|
+
}
|
|
157
|
+
export function resolveScheduleCreateCadence(input, now) {
|
|
158
|
+
const at = normalizeScheduleCadenceArg(input.at);
|
|
159
|
+
const every = normalizeScheduleCadenceArg(input.every);
|
|
160
|
+
const cron = normalizeScheduleCadenceArg(input.cron);
|
|
161
|
+
const timeZone = normalizeScheduleTimeZoneArg(input.timezone);
|
|
162
|
+
const provided = [at, every, cron].filter((value) => value !== undefined);
|
|
163
|
+
if (provided.length !== 1) {
|
|
164
|
+
throw new Error("Specify exactly one of at, every, or cron");
|
|
165
|
+
}
|
|
166
|
+
if (timeZone !== undefined && cron === undefined) {
|
|
167
|
+
throw new Error("timezone can only be used with cron");
|
|
168
|
+
}
|
|
169
|
+
if (at !== undefined) {
|
|
170
|
+
const instant = resolveScheduleAtInstant(at, now);
|
|
171
|
+
const everyMs = instant.getTime() - now.getTime();
|
|
172
|
+
if (everyMs <= 0) {
|
|
173
|
+
throw new Error(`at must be in the future: ${instant.toISOString()} is not after ${now.toISOString()}`);
|
|
174
|
+
}
|
|
175
|
+
return { cadence: { type: "every", everyMs }, oneOff: true };
|
|
176
|
+
}
|
|
177
|
+
if (every !== undefined) {
|
|
178
|
+
return { cadence: { type: "every", everyMs: parseDurationString(every) }, oneOff: false };
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
cadence: {
|
|
182
|
+
type: "cron",
|
|
183
|
+
expression: cron,
|
|
184
|
+
...(timeZone !== undefined ? { timezone: timeZone } : {}),
|
|
185
|
+
},
|
|
186
|
+
oneOff: false,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
144
189
|
function resolveScheduleUpdateExpiresAt(input) {
|
|
145
190
|
if (input.expiresIn !== undefined && input.clearExpires) {
|
|
146
191
|
throw new Error("Specify at most one of expiresIn or clearExpires");
|
|
@@ -277,18 +322,6 @@ export function createPaseoToolCatalog(options) {
|
|
|
277
322
|
return tool.handler(await parseToolInput(tool, input), context);
|
|
278
323
|
},
|
|
279
324
|
});
|
|
280
|
-
const buildCronScheduleCadence = (input) => {
|
|
281
|
-
const expression = input.cron?.trim() ?? "";
|
|
282
|
-
if (!expression) {
|
|
283
|
-
throw new Error("cron is required");
|
|
284
|
-
}
|
|
285
|
-
const timezone = normalizeScheduleTimeZoneArg(input.timezone);
|
|
286
|
-
return {
|
|
287
|
-
type: "cron",
|
|
288
|
-
expression,
|
|
289
|
-
...(timezone !== undefined ? { timezone } : {}),
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
325
|
const buildScheduleExpiry = (expiresIn) => {
|
|
293
326
|
return expiresIn === undefined
|
|
294
327
|
? undefined
|
|
@@ -427,6 +460,59 @@ export function createPaseoToolCatalog(options) {
|
|
|
427
460
|
},
|
|
428
461
|
};
|
|
429
462
|
};
|
|
463
|
+
// A schedule bound to an agent that is gone can never fire: the startup sweep
|
|
464
|
+
// completes it on sight. Fail loudly at create time instead of handing back a
|
|
465
|
+
// schedule that reads active and is already dead.
|
|
466
|
+
const assertScheduleTargetAgentExists = async (agentId) => {
|
|
467
|
+
if (agentManager.getAgent(agentId)) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const record = await agentStorage.get(agentId);
|
|
471
|
+
if (!record || record.internal) {
|
|
472
|
+
throw new Error(`Agent ${agentId} not found`);
|
|
473
|
+
}
|
|
474
|
+
if (record.archivedAt) {
|
|
475
|
+
throw new Error(`Agent ${agentId} is archived and cannot be a schedule target`);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const resolveScheduleCreateTarget = async (params) => {
|
|
479
|
+
const target = params.target?.trim();
|
|
480
|
+
// Default stays new-agent so callers written against the narrower schema
|
|
481
|
+
// keep the behaviour they had before target existed.
|
|
482
|
+
if (!target || target === "new-agent") {
|
|
483
|
+
return resolveNewAgentScheduleTarget({
|
|
484
|
+
...(params.provider !== undefined ? { provider: params.provider } : {}),
|
|
485
|
+
...(params.cwd !== undefined ? { cwd: params.cwd } : {}),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (params.provider !== undefined || params.cwd !== undefined) {
|
|
489
|
+
throw new Error("provider and cwd can only be used with target new-agent");
|
|
490
|
+
}
|
|
491
|
+
if (target === "self") {
|
|
492
|
+
if (!callerAgentId) {
|
|
493
|
+
throw new Error('target "self" requires an agent-scoped session');
|
|
494
|
+
}
|
|
495
|
+
resolveCallerAgent();
|
|
496
|
+
return { type: "agent", agentId: callerAgentId };
|
|
497
|
+
}
|
|
498
|
+
await assertScheduleTargetAgentExists(target);
|
|
499
|
+
return { type: "agent", agentId: target };
|
|
500
|
+
};
|
|
501
|
+
// Recurring creates go through createOrReplace so an agent re-registering the
|
|
502
|
+
// same standing schedule refreshes it instead of minting a twin. A one-off must
|
|
503
|
+
// NOT: createOrReplace carries the existing schedule's `runs` forward, and
|
|
504
|
+
// shouldCompleteSchedule counts them against the incoming maxRuns before tick
|
|
505
|
+
// looks at nextRunAt, so replacing a schedule that has already run with
|
|
506
|
+
// maxRuns 1 completes it on the next tick without ever firing. There is also
|
|
507
|
+
// nothing to make idempotent about a one-off: two calls mean two reminders.
|
|
508
|
+
// Covered by schedule/service.test.ts "replacing a schedule that has run with
|
|
509
|
+
// maxRuns 1 buries it without firing".
|
|
510
|
+
const persistNewSchedule = async (input, oneOff) => {
|
|
511
|
+
if (!scheduleService) {
|
|
512
|
+
throw new Error("Schedule service is not configured");
|
|
513
|
+
}
|
|
514
|
+
return oneOff ? scheduleService.create(input) : scheduleService.createOrReplace(input);
|
|
515
|
+
};
|
|
430
516
|
const ProviderModelInputSchema = AgentProviderEnum.trim()
|
|
431
517
|
.refine((value) => value.includes("/"), {
|
|
432
518
|
message: "provider must be provider/model, for example codex/gpt-5.4",
|
|
@@ -1609,39 +1695,80 @@ export function createPaseoToolCatalog(options) {
|
|
|
1609
1695
|
});
|
|
1610
1696
|
registerTool("create_schedule", {
|
|
1611
1697
|
title: "Create schedule",
|
|
1612
|
-
description: "
|
|
1698
|
+
description: "Schedule a prompt for later: once at a specific time (at), on a repeating interval (every), or on a cron cadence (cron). The prompt can start a fresh agent, come back to you (target self), or go to another agent by id. Prefer this over an in-session loop whenever the work must survive this session ending, since schedules are owned by the daemon.",
|
|
1613
1699
|
inputSchema: {
|
|
1614
1700
|
prompt: z.string().trim().min(1, "prompt is required"),
|
|
1615
|
-
|
|
1701
|
+
at: z
|
|
1702
|
+
.string()
|
|
1703
|
+
.trim()
|
|
1704
|
+
.min(1)
|
|
1705
|
+
.optional()
|
|
1706
|
+
.describe('One-off delivery time. An ISO 8601 instant ("2026-08-21T21:30:00-03:00") or a duration from now ("90m", "2h30m"). Runs exactly once. Mutually exclusive with every and cron.'),
|
|
1707
|
+
every: z
|
|
1708
|
+
.string()
|
|
1709
|
+
.trim()
|
|
1710
|
+
.min(1)
|
|
1711
|
+
.optional()
|
|
1712
|
+
.describe('Repeating interval, for example "5m", "1h", "2h30m", "1d". Mutually exclusive with at and cron.'),
|
|
1713
|
+
cron: z
|
|
1714
|
+
.string()
|
|
1715
|
+
.trim()
|
|
1716
|
+
.min(1)
|
|
1717
|
+
.optional()
|
|
1718
|
+
.describe('Repeating cron expression, for example "0 9 * * 1-5". Mutually exclusive with at and every.'),
|
|
1616
1719
|
timezone: z
|
|
1617
1720
|
.string()
|
|
1618
1721
|
.trim()
|
|
1619
1722
|
.min(1)
|
|
1620
1723
|
.optional()
|
|
1621
|
-
.describe("IANA time zone for the cron cadence. For example: America/New_York."),
|
|
1622
|
-
name: z
|
|
1623
|
-
|
|
1624
|
-
|
|
1724
|
+
.describe("IANA time zone for the cron cadence. Requires cron. For example: America/New_York."),
|
|
1725
|
+
name: z
|
|
1726
|
+
.string()
|
|
1727
|
+
.optional()
|
|
1728
|
+
.describe("Reusing a name with the same target refreshes that schedule in place instead of creating a second one."),
|
|
1729
|
+
target: z
|
|
1730
|
+
.string()
|
|
1731
|
+
.trim()
|
|
1732
|
+
.min(1)
|
|
1733
|
+
.optional()
|
|
1734
|
+
.describe('Who receives the prompt: "new-agent" (default) starts a fresh agent each run, "self" delivers to you, or an agent id delivers to that agent.'),
|
|
1735
|
+
provider: AgentProviderEnum.optional().describe("Provider, or provider/model (for example: codex or codex/gpt-5.4). Only valid with target new-agent."),
|
|
1736
|
+
cwd: z.string().optional().describe("Working directory. Only valid with target new-agent."),
|
|
1625
1737
|
maxRuns: z.number().int().positive().optional(),
|
|
1626
1738
|
expiresIn: z.string().optional(),
|
|
1739
|
+
runNow: z
|
|
1740
|
+
.boolean()
|
|
1741
|
+
.optional()
|
|
1742
|
+
.describe("Also fire once immediately on create. Defaults to true for every, false for cron. Not allowed with at."),
|
|
1627
1743
|
},
|
|
1628
1744
|
outputSchema: ScheduleSummarySchema.shape,
|
|
1629
|
-
}, async ({ prompt, cron, timezone, name, provider, cwd, maxRuns, expiresIn }) => {
|
|
1745
|
+
}, async ({ prompt, at, every, cron, timezone, name, target, provider, cwd, maxRuns, expiresIn, runNow, }) => {
|
|
1630
1746
|
if (!scheduleService) {
|
|
1631
1747
|
throw new Error("Schedule service is not configured");
|
|
1632
1748
|
}
|
|
1749
|
+
const { cadence, oneOff } = resolveScheduleCreateCadence({ at, every, cron, timezone }, new Date());
|
|
1750
|
+
if (oneOff && maxRuns !== undefined && maxRuns !== 1) {
|
|
1751
|
+
throw new Error("maxRuns cannot be combined with at: an at schedule runs exactly once");
|
|
1752
|
+
}
|
|
1753
|
+
if (oneOff && runNow === true) {
|
|
1754
|
+
throw new Error("runNow cannot be combined with at: an at schedule runs only at its time");
|
|
1755
|
+
}
|
|
1756
|
+
const resolvedTarget = await resolveScheduleCreateTarget({ target, provider, cwd });
|
|
1633
1757
|
const expiresAt = buildScheduleExpiry(expiresIn);
|
|
1634
|
-
const
|
|
1758
|
+
const scheduleInput = {
|
|
1635
1759
|
prompt: prompt.trim(),
|
|
1636
|
-
cadence
|
|
1637
|
-
|
|
1638
|
-
...(timezone !== undefined ? { timezone } : {}),
|
|
1639
|
-
}),
|
|
1640
|
-
target: resolveNewAgentScheduleTarget({ provider, cwd }),
|
|
1760
|
+
cadence,
|
|
1761
|
+
target: resolvedTarget,
|
|
1641
1762
|
...(name?.trim() ? { name: name.trim() } : {}),
|
|
1642
|
-
...(
|
|
1763
|
+
...(oneOff
|
|
1764
|
+
? { maxRuns: 1, runOnCreate: false }
|
|
1765
|
+
: {
|
|
1766
|
+
...(maxRuns === undefined ? {} : { maxRuns }),
|
|
1767
|
+
...(runNow === undefined ? {} : { runOnCreate: runNow }),
|
|
1768
|
+
}),
|
|
1643
1769
|
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
1644
|
-
}
|
|
1770
|
+
};
|
|
1771
|
+
const schedule = await persistNewSchedule(scheduleInput, oneOff);
|
|
1645
1772
|
return {
|
|
1646
1773
|
content: [],
|
|
1647
1774
|
structuredContent: ensureValidJson(toScheduleSummary(schedule)),
|
|
@@ -1649,22 +1776,43 @@ export function createPaseoToolCatalog(options) {
|
|
|
1649
1776
|
});
|
|
1650
1777
|
registerTool("create_heartbeat", {
|
|
1651
1778
|
title: "Create heartbeat",
|
|
1652
|
-
description: "
|
|
1779
|
+
description: "Schedule a prompt back to yourself: once at a specific time (at), on a repeating interval (every), or on a cron cadence (cron). Equivalent to create_schedule with target self.",
|
|
1653
1780
|
inputSchema: {
|
|
1654
1781
|
prompt: z.string().trim().min(1, "prompt is required"),
|
|
1655
|
-
|
|
1782
|
+
at: z
|
|
1783
|
+
.string()
|
|
1784
|
+
.trim()
|
|
1785
|
+
.min(1)
|
|
1786
|
+
.optional()
|
|
1787
|
+
.describe('One-off delivery time. An ISO 8601 instant ("2026-08-21T21:30:00-03:00") or a duration from now ("90m"). Runs exactly once. Mutually exclusive with every and cron.'),
|
|
1788
|
+
every: z
|
|
1789
|
+
.string()
|
|
1790
|
+
.trim()
|
|
1791
|
+
.min(1)
|
|
1792
|
+
.optional()
|
|
1793
|
+
.describe('Repeating interval, for example "5m", "1h", "2h30m". Mutually exclusive with at and cron.'),
|
|
1794
|
+
cron: z
|
|
1795
|
+
.string()
|
|
1796
|
+
.trim()
|
|
1797
|
+
.min(1)
|
|
1798
|
+
.optional()
|
|
1799
|
+
.describe('Repeating cron expression, for example "0 9 * * 1-5". Mutually exclusive with at and every.'),
|
|
1656
1800
|
timezone: z
|
|
1657
1801
|
.string()
|
|
1658
1802
|
.trim()
|
|
1659
1803
|
.min(1)
|
|
1660
1804
|
.optional()
|
|
1661
|
-
.describe("IANA time zone for the cron cadence. For example: America/New_York."),
|
|
1805
|
+
.describe("IANA time zone for the cron cadence. Requires cron. For example: America/New_York."),
|
|
1662
1806
|
name: z.string().optional(),
|
|
1663
1807
|
maxRuns: z.number().int().positive().optional(),
|
|
1664
1808
|
expiresIn: z.string().optional(),
|
|
1809
|
+
runNow: z
|
|
1810
|
+
.boolean()
|
|
1811
|
+
.optional()
|
|
1812
|
+
.describe("Also fire once immediately on create. Defaults to true for every, false for cron. Not allowed with at."),
|
|
1665
1813
|
},
|
|
1666
1814
|
outputSchema: ScheduleSummarySchema.shape,
|
|
1667
|
-
}, async ({ prompt, cron, timezone, name, maxRuns, expiresIn }) => {
|
|
1815
|
+
}, async ({ prompt, at, every, cron, timezone, name, maxRuns, expiresIn, runNow }) => {
|
|
1668
1816
|
if (!scheduleService) {
|
|
1669
1817
|
throw new Error("Schedule service is not configured");
|
|
1670
1818
|
}
|
|
@@ -1672,23 +1820,50 @@ export function createPaseoToolCatalog(options) {
|
|
|
1672
1820
|
throw new Error("create_heartbeat requires an agent-scoped session");
|
|
1673
1821
|
}
|
|
1674
1822
|
resolveCallerAgent();
|
|
1823
|
+
const { cadence, oneOff } = resolveScheduleCreateCadence({ at, every, cron, timezone }, new Date());
|
|
1824
|
+
if (oneOff && maxRuns !== undefined && maxRuns !== 1) {
|
|
1825
|
+
throw new Error("maxRuns cannot be combined with at: an at schedule runs exactly once");
|
|
1826
|
+
}
|
|
1827
|
+
if (oneOff && runNow === true) {
|
|
1828
|
+
throw new Error("runNow cannot be combined with at: an at schedule runs only at its time");
|
|
1829
|
+
}
|
|
1675
1830
|
const expiresAt = buildScheduleExpiry(expiresIn);
|
|
1676
|
-
const
|
|
1831
|
+
const scheduleInput = {
|
|
1677
1832
|
prompt: prompt.trim(),
|
|
1678
|
-
cadence
|
|
1679
|
-
cron,
|
|
1680
|
-
...(timezone !== undefined ? { timezone } : {}),
|
|
1681
|
-
}),
|
|
1833
|
+
cadence,
|
|
1682
1834
|
target: { type: "agent", agentId: callerAgentId },
|
|
1683
1835
|
...(name?.trim() ? { name: name.trim() } : {}),
|
|
1684
|
-
...(
|
|
1836
|
+
...(oneOff
|
|
1837
|
+
? { maxRuns: 1, runOnCreate: false }
|
|
1838
|
+
: {
|
|
1839
|
+
...(maxRuns === undefined ? {} : { maxRuns }),
|
|
1840
|
+
...(runNow === undefined ? {} : { runOnCreate: runNow }),
|
|
1841
|
+
}),
|
|
1685
1842
|
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
1686
|
-
}
|
|
1843
|
+
};
|
|
1844
|
+
const schedule = await persistNewSchedule(scheduleInput, oneOff);
|
|
1687
1845
|
return {
|
|
1688
1846
|
content: [],
|
|
1689
1847
|
structuredContent: ensureValidJson(toScheduleSummary(schedule)),
|
|
1690
1848
|
};
|
|
1691
1849
|
});
|
|
1850
|
+
registerTool("run_schedule_once", {
|
|
1851
|
+
title: "Run schedule once",
|
|
1852
|
+
description: "Fire a schedule immediately, out of band. The cadence and the next scheduled run are left untouched, but the run is recorded and counts toward maxRuns.",
|
|
1853
|
+
inputSchema: {
|
|
1854
|
+
id: z.string(),
|
|
1855
|
+
},
|
|
1856
|
+
outputSchema: StoredScheduleSchema.shape,
|
|
1857
|
+
}, async ({ id }) => {
|
|
1858
|
+
if (!scheduleService) {
|
|
1859
|
+
throw new Error("Schedule service is not configured");
|
|
1860
|
+
}
|
|
1861
|
+
const schedule = await scheduleService.runOnce(id);
|
|
1862
|
+
return {
|
|
1863
|
+
content: [],
|
|
1864
|
+
structuredContent: ensureValidJson(schedule),
|
|
1865
|
+
};
|
|
1866
|
+
});
|
|
1692
1867
|
registerTool("list_schedules", {
|
|
1693
1868
|
title: "List schedules",
|
|
1694
1869
|
description: "List all schedules managed by the daemon.",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* - `create_terminal`, `kill_terminal`, `send_terminal_keys` arbitrary command execution.
|
|
43
43
|
* - `create_schedule`, `update_schedule`, `delete_schedule`,
|
|
44
44
|
* `pause_schedule`, `resume_schedule`, `create_heartbeat` persistent side effects.
|
|
45
|
+
* - `run_schedule_once` fires a schedule now, which starts or prompts an agent.
|
|
45
46
|
* - `rename_workspace` mutates.
|
|
46
47
|
* - `speak` an outward side effect, and voice is out of v1 scope.
|
|
47
48
|
* - `wait_for_agent` not mutating, but it BLOCKS. An event-woken judge
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* - `create_terminal`, `kill_terminal`, `send_terminal_keys` arbitrary command execution.
|
|
43
43
|
* - `create_schedule`, `update_schedule`, `delete_schedule`,
|
|
44
44
|
* `pause_schedule`, `resume_schedule`, `create_heartbeat` persistent side effects.
|
|
45
|
+
* - `run_schedule_once` fires a schedule now, which starts or prompts an agent.
|
|
45
46
|
* - `rename_workspace` mutates.
|
|
46
47
|
* - `speak` an outward side effect, and voice is out of v1 scope.
|
|
47
48
|
* - `wait_for_agent` not mutating, but it BLOCKS. An event-woken judge
|
|
@@ -41,6 +41,7 @@ export function buildConfigOverrides(record) {
|
|
|
41
41
|
extra: record.config?.extra ?? undefined,
|
|
42
42
|
systemPrompt: record.config?.systemPrompt ?? undefined,
|
|
43
43
|
mcpServers: record.config?.mcpServers ?? undefined,
|
|
44
|
+
allowedTools: record.config?.allowedTools ?? undefined,
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
export function buildSessionConfig(record, options) {
|
|
@@ -58,6 +59,7 @@ export function buildSessionConfig(record, options) {
|
|
|
58
59
|
extra: overrides.extra,
|
|
59
60
|
systemPrompt: overrides.systemPrompt,
|
|
60
61
|
mcpServers: overrides.mcpServers,
|
|
62
|
+
allowedTools: overrides.allowedTools,
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
export function isStoredAgentProviderAvailable(record, validProviders) {
|
package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts
CHANGED
|
@@ -19,9 +19,21 @@ export interface ResolveOrCreateWorkspaceIdInput {
|
|
|
19
19
|
cwd: string;
|
|
20
20
|
initialTitle: string | null;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* `createdWorkspace` is true ONLY when this call minted a brand new workspace
|
|
24
|
+
* record. Callers use it to decide whether post-create side effects that are
|
|
25
|
+
* only correct on a fresh workspace may run — notably workspace auto-naming,
|
|
26
|
+
* which would otherwise rename a workspace the user already had. Deriving that
|
|
27
|
+
* from the REQUEST (`!msg.workspaceId`) is wrong now that an omitted id can
|
|
28
|
+
* resolve to an existing workspace.
|
|
29
|
+
*/
|
|
30
|
+
export interface ResolveOrCreateWorkspaceIdResult {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
createdWorkspace: boolean;
|
|
33
|
+
}
|
|
22
34
|
export interface WorkspaceProvisioningService {
|
|
23
35
|
findOrCreateWorkspaceForDirectory(cwd: string): Promise<PersistedWorkspaceRecord>;
|
|
24
|
-
resolveOrCreateWorkspaceIdForCreateAgent(input: ResolveOrCreateWorkspaceIdInput): Promise<
|
|
36
|
+
resolveOrCreateWorkspaceIdForCreateAgent(input: ResolveOrCreateWorkspaceIdInput): Promise<ResolveOrCreateWorkspaceIdResult>;
|
|
25
37
|
createWorkspaceForDirectory(cwd: string, title?: string | null): Promise<PersistedWorkspaceRecord>;
|
|
26
38
|
findOrCreateProjectForDirectory(cwd: string): Promise<PersistedProjectRecord>;
|
|
27
39
|
ensureWorkspaceRecordUnarchived(workspace: PersistedWorkspaceRecord): Promise<PersistedWorkspaceRecord>;
|
|
@@ -119,12 +119,36 @@ export function createWorkspaceProvisioningService(deps) {
|
|
|
119
119
|
}
|
|
120
120
|
async function resolveOrCreateWorkspaceIdForCreateAgent(input) {
|
|
121
121
|
if (input.createdWorktree) {
|
|
122
|
-
return
|
|
122
|
+
return {
|
|
123
|
+
workspaceId: input.createdWorktree.workspace.workspaceId,
|
|
124
|
+
createdWorkspace: false,
|
|
125
|
+
};
|
|
123
126
|
}
|
|
124
127
|
if (input.requestedWorkspaceId) {
|
|
125
|
-
return input.requestedWorkspaceId;
|
|
128
|
+
return { workspaceId: input.requestedWorkspaceId, createdWorkspace: false };
|
|
126
129
|
}
|
|
127
|
-
|
|
130
|
+
// Look before you mint. This branch used to call createWorkspaceForDirectory
|
|
131
|
+
// unconditionally, so every createAgent that omitted `workspaceId` produced a
|
|
132
|
+
// SECOND workspace for a directory that already had one — no lookup, not even
|
|
133
|
+
// against the identical cwd. On one daemon that turned 19 directories into 114
|
|
134
|
+
// workspace records, 48 of them on a single repo root, and the agent surfaced
|
|
135
|
+
// in the fresh workspace instead of the one on screen.
|
|
136
|
+
//
|
|
137
|
+
// 0.3.38 fixed the palette by making its caller pass the id. That closes one
|
|
138
|
+
// door and leaves the hole: any other caller (MCP create_agent, loops,
|
|
139
|
+
// schedules, whatever is written next) still mints. Fix it where ownership is
|
|
140
|
+
// actually decided.
|
|
141
|
+
const existing = await findExactWorkspaceByDirectory(input.cwd, { refreshGit: false });
|
|
142
|
+
if (existing) {
|
|
143
|
+
const reused = await reclassifyOrUnarchiveWorkspaceForDirectory({
|
|
144
|
+
workspace: existing,
|
|
145
|
+
project: await projectRegistry.get(existing.projectId),
|
|
146
|
+
cwd: await resolveWorkspaceDirectory(input.cwd, { refreshGit: false }),
|
|
147
|
+
});
|
|
148
|
+
return { workspaceId: reused.workspaceId, createdWorkspace: false };
|
|
149
|
+
}
|
|
150
|
+
const created = await createWorkspaceForDirectory(input.cwd, input.initialTitle);
|
|
151
|
+
return { workspaceId: created.workspaceId, createdWorkspace: true };
|
|
128
152
|
}
|
|
129
153
|
async function createWorkspaceForDirectory(cwd, title) {
|
|
130
154
|
const checkout = await workspaceGitService.getCheckout(cwd);
|
|
@@ -2693,13 +2693,17 @@ export class Session {
|
|
|
2693
2693
|
let createAgentConfig = createdWorktree
|
|
2694
2694
|
? { ...config, cwd: createdWorktree.worktree.worktreePath }
|
|
2695
2695
|
: config;
|
|
2696
|
-
const workspaceId = await this.workspaceProvisioning.resolveOrCreateWorkspaceIdForCreateAgent({
|
|
2696
|
+
const { workspaceId, createdWorkspace } = await this.workspaceProvisioning.resolveOrCreateWorkspaceIdForCreateAgent({
|
|
2697
2697
|
createdWorktree,
|
|
2698
2698
|
requestedWorkspaceId: msg.workspaceId,
|
|
2699
2699
|
cwd: createAgentConfig.cwd,
|
|
2700
2700
|
initialTitle: workspacePromptTitle,
|
|
2701
2701
|
});
|
|
2702
|
-
|
|
2702
|
+
// Gate auto-naming on what actually happened, NOT on what was requested.
|
|
2703
|
+
// An omitted workspaceId no longer implies a fresh workspace: provisioning
|
|
2704
|
+
// now reuses the one that already owns this cwd, and auto-naming a reused
|
|
2705
|
+
// workspace would rename one the user already had.
|
|
2706
|
+
const createdDirectoryWorkspaceForAgent = !createdWorktree && createdWorkspace;
|
|
2703
2707
|
// Resuming a past session: the recorded cwd may no longer exist on this
|
|
2704
2708
|
// machine (different $HOME, deleted folder). The transcript is read from
|
|
2705
2709
|
// ~/.claude/projects regardless, so fall back to an existing directory
|