@wrongstack/core 0.302.2 → 0.305.0
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 +1 -1
- package/dist/chronicle/index.js +117 -30
- package/dist/chronicle/project-server.js +12 -5
- package/dist/coordination/agents/index.js +4645 -3448
- package/dist/coordination/agents/project-agent-auto-optimize.d.ts +116 -0
- package/dist/coordination/agents/project-agent-capture-window.d.ts +29 -0
- package/dist/coordination/agents/project-agent-config-io.d.ts +11 -0
- package/dist/coordination/agents/project-agent-consolidation.d.ts +34 -2
- package/dist/coordination/agents/project-agent-directive-outcome.d.ts +57 -0
- package/dist/coordination/agents/project-agent-files.d.ts +12 -3
- package/dist/coordination/agents/project-agent-identity-types.d.ts +4 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +38 -11
- package/dist/coordination/agents/project-agent-learning-entries.d.ts +8 -2
- package/dist/coordination/agents/project-agent-learning-policy.d.ts +22 -1
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +72 -1
- package/dist/coordination/agents/project-agent-optimizer.d.ts +49 -0
- package/dist/coordination/agents/project-agent-quarantine.d.ts +63 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +146 -0
- package/dist/coordination/agents/role-skills.d.ts +11 -1
- package/dist/coordination/agents/types.d.ts +10 -2
- package/dist/coordination/director-prompts.d.ts +19 -6
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/fleet.d.ts +0 -6
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +5746 -4326
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/core/agent-types.d.ts +4 -2
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/context.d.ts +19 -0
- package/dist/core/conversation-state.d.ts +14 -0
- package/dist/core/fallback-profile-manager.d.ts +70 -2
- package/dist/core/index.js +317 -108
- package/dist/core/system-prompt-blocks.d.ts +1 -1
- package/dist/core/system-prompt-builder.d.ts +13 -1
- package/dist/core/system-prompt-glossary.d.ts +73 -0
- package/dist/core/system-prompt-memory-skills.d.ts +2 -2
- package/dist/defaults/index.js +1635 -1120
- package/dist/execution/council-orchestrator.d.ts +3 -13
- package/dist/execution/index.js +3136 -2706
- package/dist/execution/one-shot-llm.d.ts +5 -0
- package/dist/goal/index.js +7 -0
- package/dist/hq/index.js +17 -7
- package/dist/hq/protocol/kanban.d.ts +21 -0
- package/dist/hq/protocol.js +5 -1
- package/dist/hq/redaction.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5502 -3483
- package/dist/infrastructure/index.js +247 -122
- package/dist/kernel/events/agent-events.d.ts +28 -0
- package/dist/plugin/index.js +111 -7
- package/dist/registry/index.js +11 -0
- package/dist/registry/tool-registry.d.ts +8 -0
- package/dist/replay/hash.d.ts +9 -0
- package/dist/replay/index.js +14 -4
- package/dist/replay/replay-provider-runner.d.ts +31 -1
- package/dist/security/index.js +92 -21
- package/dist/security/kanban-boundary.d.ts +5 -1
- package/dist/security/secret-vault.d.ts +2 -0
- package/dist/session-catalog/index.js +86 -10
- package/dist/session-catalog/project-server.js +100 -17
- package/dist/session-catalog/protocol.d.ts +20 -4
- package/dist/session-catalog/store.d.ts +19 -3
- package/dist/storage/index.js +305 -69
- package/dist/storage/memory-consolidator.d.ts +4 -2
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/session-resume-validation.d.ts +24 -0
- package/dist/storage/session-store/directory-scan.d.ts +5 -1
- package/dist/storage/session-store/fork-session.d.ts +13 -1
- package/dist/storage/session-store/load-cache.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +5 -0
- package/dist/storage/session-store.d.ts +18 -0
- package/dist/tasking/index.js +5 -0
- package/dist/tools/index.js +1151 -831
- package/dist/types/config/mcp-features.d.ts +31 -1
- package/dist/types/config/root.d.ts +23 -1
- package/dist/types/config/skills-fleet-brain.d.ts +34 -0
- package/dist/types/config/tools.d.ts +22 -0
- package/dist/types/config/ui.d.ts +14 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/default-config.d.ts +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +23 -0
- package/dist/types/multi-agent.d.ts +7 -0
- package/dist/types/session.d.ts +9 -1
- package/dist/types/task-graph.d.ts +2 -0
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +217 -76
- package/dist/utils/project-state-guard.d.ts +21 -0
- package/dist/utils/session-scoped-path.d.ts +17 -0
- package/dist/utils/todos-format.d.ts +20 -0
- package/instructions/leader-after-task.md +3 -4
- package/instructions/system-lite.md +17 -14
- package/instructions/system-pro.md +29 -30
- package/instructions/system.md +29 -28
- package/package.json +3 -3
- package/skills/wrongstack-kanban/SKILL.md +95 -93
|
@@ -1198,6 +1198,22 @@ function isPidAlive(pid) {
|
|
|
1198
1198
|
}
|
|
1199
1199
|
}
|
|
1200
1200
|
|
|
1201
|
+
// src/utils/session-scoped-path.ts
|
|
1202
|
+
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
1203
|
+
".replay.jsonl",
|
|
1204
|
+
".audit.jsonl",
|
|
1205
|
+
".annotations.jsonl"
|
|
1206
|
+
];
|
|
1207
|
+
var RESERVED_SESSION_JSONL_NAMES = /* @__PURE__ */ new Set([
|
|
1208
|
+
"_index.jsonl",
|
|
1209
|
+
"_mailbox.jsonl"
|
|
1210
|
+
]);
|
|
1211
|
+
function isSessionTranscriptFileName(name) {
|
|
1212
|
+
if (!name.endsWith(".jsonl")) return false;
|
|
1213
|
+
if (RESERVED_SESSION_JSONL_NAMES.has(name)) return false;
|
|
1214
|
+
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1201
1217
|
// src/session-catalog/store.ts
|
|
1202
1218
|
var SCHEMA_VERSION = 1;
|
|
1203
1219
|
var MAX_LEASE_MS = 12e4;
|
|
@@ -1392,6 +1408,25 @@ var SessionCatalogStore = class {
|
|
|
1392
1408
|
leaseRow(sessionId) {
|
|
1393
1409
|
return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
|
|
1394
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* True when `sessionId` has a live `session_leases` row owned by a
|
|
1413
|
+
* different OS process than the *caller*. Used by
|
|
1414
|
+
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
1415
|
+
* must be fenced off) from "this very TUI owning its own session"
|
|
1416
|
+
* (which is allowed to claim a non-destructive maintenance lease on
|
|
1417
|
+
* its own transcript).
|
|
1418
|
+
*
|
|
1419
|
+
* `callerPid` must be the pid of the process that asked for the lease,
|
|
1420
|
+
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
1421
|
+
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
1422
|
+
* would mark every lease — including the caller's own session — foreign,
|
|
1423
|
+
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
1424
|
+
* that run the store in-process may omit it.
|
|
1425
|
+
*/
|
|
1426
|
+
foreignLiveLease(sessionId, callerPid = process.pid) {
|
|
1427
|
+
const live = this.leaseRow(sessionId);
|
|
1428
|
+
return Boolean(live) && live.owner_pid !== callerPid;
|
|
1429
|
+
}
|
|
1395
1430
|
verifyCredential(credential) {
|
|
1396
1431
|
assertId(credential.sessionId);
|
|
1397
1432
|
const row = this.leaseRow(credential.sessionId);
|
|
@@ -1664,13 +1699,51 @@ var SessionCatalogStore = class {
|
|
|
1664
1699
|
damaged: row.damaged !== 0
|
|
1665
1700
|
};
|
|
1666
1701
|
}
|
|
1667
|
-
listCatalog(
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
)
|
|
1702
|
+
listCatalog(criteria = {}) {
|
|
1703
|
+
const requestedLimit = criteria.limit ?? 100;
|
|
1704
|
+
if (!Number.isFinite(requestedLimit)) throw new TypeError("Invalid session catalog limit");
|
|
1705
|
+
const bounded = Math.min(MAX_PAGE, Math.max(1, Math.floor(requestedLimit)));
|
|
1706
|
+
const clauses = [];
|
|
1707
|
+
const values = [];
|
|
1708
|
+
const jsonText = (field) => `CASE WHEN json_valid(summary_json) THEN json_extract(summary_json,'$.${field}') END`;
|
|
1709
|
+
const literalLike = (value) => `%${value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
|
|
1710
|
+
const search = criteria.search?.trim();
|
|
1711
|
+
if (search) {
|
|
1712
|
+
const pattern = literalLike(search);
|
|
1713
|
+
clauses.push(
|
|
1714
|
+
`(session_id LIKE ? ESCAPE '\\' OR ${jsonText("title")} LIKE ? ESCAPE '\\' OR ${jsonText("name")} LIKE ? ESCAPE '\\')`
|
|
1715
|
+
);
|
|
1716
|
+
values.push(pattern, pattern, pattern);
|
|
1717
|
+
}
|
|
1718
|
+
if (criteria.since) {
|
|
1719
|
+
clauses.push(`${jsonText("startedAt")}>=?`);
|
|
1720
|
+
values.push(criteria.since);
|
|
1721
|
+
}
|
|
1722
|
+
if (criteria.until) {
|
|
1723
|
+
clauses.push(`${jsonText("startedAt")}<=?`);
|
|
1724
|
+
values.push(criteria.until);
|
|
1725
|
+
}
|
|
1726
|
+
if (criteria.provider) {
|
|
1727
|
+
clauses.push(`${jsonText("provider")}=?`);
|
|
1728
|
+
values.push(criteria.provider);
|
|
1729
|
+
}
|
|
1730
|
+
if (criteria.model) {
|
|
1731
|
+
clauses.push(`${jsonText("model")}=?`);
|
|
1732
|
+
values.push(criteria.model);
|
|
1733
|
+
}
|
|
1734
|
+
if (criteria.minTokens !== void 0) {
|
|
1735
|
+
if (!Number.isFinite(criteria.minTokens)) throw new TypeError("Invalid minimum token count");
|
|
1736
|
+
clauses.push(`CAST(COALESCE(${jsonText("tokenTotal")},0) AS REAL)>=?`);
|
|
1737
|
+
values.push(criteria.minTokens);
|
|
1738
|
+
}
|
|
1739
|
+
if (criteria.titleContains) {
|
|
1740
|
+
clauses.push(`${jsonText("title")} LIKE ? ESCAPE '\\'`);
|
|
1741
|
+
values.push(literalLike(criteria.titleContains.toLocaleLowerCase()));
|
|
1742
|
+
}
|
|
1743
|
+
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
1744
|
+
const rows = this.db.prepare(
|
|
1745
|
+
`SELECT * FROM sessions${where} ORDER BY COALESCE(${jsonText("lastActivityAt")},${jsonText("endedAt")},${jsonText("startedAt")}) DESC,${jsonText("startedAt")} DESC,session_id ASC LIMIT ?`
|
|
1746
|
+
).all(...values, bounded);
|
|
1674
1747
|
return rows.map((row) => this.catalogRecord(row));
|
|
1675
1748
|
}
|
|
1676
1749
|
getSummary(sessionId) {
|
|
@@ -1725,11 +1798,14 @@ var SessionCatalogStore = class {
|
|
|
1725
1798
|
throw error;
|
|
1726
1799
|
}
|
|
1727
1800
|
}
|
|
1728
|
-
acquireMaintenance(sessionId, operation, holderId, leaseMs) {
|
|
1801
|
+
acquireMaintenance(sessionId, operation, holderId, leaseMs, holderPid) {
|
|
1729
1802
|
assertId(sessionId);
|
|
1730
1803
|
return this.transaction(() => {
|
|
1731
1804
|
this.reapExpired();
|
|
1732
|
-
|
|
1805
|
+
const live = this.leaseRow(sessionId);
|
|
1806
|
+
if (live && (operation === "delete" || this.foreignLiveLease(sessionId, holderPid))) {
|
|
1807
|
+
throw conflict(`Session ${sessionId} is live`);
|
|
1808
|
+
}
|
|
1733
1809
|
const reservation = this.db.prepare(
|
|
1734
1810
|
"SELECT 1 AS yes FROM resume_reservations WHERE target_session_id=? AND expires_at>?"
|
|
1735
1811
|
).get(sessionId, Date.now());
|
|
@@ -1822,7 +1898,7 @@ var SessionCatalogStore = class {
|
|
|
1822
1898
|
rebuildCatalog() {
|
|
1823
1899
|
const summaries = this.walkFiles(this.sessionsDir, ".summary.json");
|
|
1824
1900
|
const transcripts = this.walkFiles(this.sessionsDir, ".jsonl").filter(
|
|
1825
|
-
(file) =>
|
|
1901
|
+
(file) => isSessionTranscriptFileName(path4.basename(file))
|
|
1826
1902
|
);
|
|
1827
1903
|
const ids = /* @__PURE__ */ new Set();
|
|
1828
1904
|
for (const file of [...summaries, ...transcripts]) {
|
|
@@ -567,6 +567,22 @@ function isPidAlive(pid) {
|
|
|
567
567
|
}
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
// src/utils/session-scoped-path.ts
|
|
571
|
+
var SESSION_SIDECAR_JSONL_SUFFIXES = [
|
|
572
|
+
".replay.jsonl",
|
|
573
|
+
".audit.jsonl",
|
|
574
|
+
".annotations.jsonl"
|
|
575
|
+
];
|
|
576
|
+
var RESERVED_SESSION_JSONL_NAMES = /* @__PURE__ */ new Set([
|
|
577
|
+
"_index.jsonl",
|
|
578
|
+
"_mailbox.jsonl"
|
|
579
|
+
]);
|
|
580
|
+
function isSessionTranscriptFileName(name) {
|
|
581
|
+
if (!name.endsWith(".jsonl")) return false;
|
|
582
|
+
if (RESERVED_SESSION_JSONL_NAMES.has(name)) return false;
|
|
583
|
+
return !SESSION_SIDECAR_JSONL_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
584
|
+
}
|
|
585
|
+
|
|
570
586
|
// src/session-catalog/store.ts
|
|
571
587
|
var SCHEMA_VERSION = 1;
|
|
572
588
|
var MAX_LEASE_MS = 12e4;
|
|
@@ -761,6 +777,25 @@ var SessionCatalogStore = class {
|
|
|
761
777
|
leaseRow(sessionId) {
|
|
762
778
|
return this.db.prepare("SELECT * FROM session_leases WHERE session_id=?").get(sessionId);
|
|
763
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* True when `sessionId` has a live `session_leases` row owned by a
|
|
782
|
+
* different OS process than the *caller*. Used by
|
|
783
|
+
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
784
|
+
* must be fenced off) from "this very TUI owning its own session"
|
|
785
|
+
* (which is allowed to claim a non-destructive maintenance lease on
|
|
786
|
+
* its own transcript).
|
|
787
|
+
*
|
|
788
|
+
* `callerPid` must be the pid of the process that asked for the lease,
|
|
789
|
+
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
790
|
+
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
791
|
+
* would mark every lease — including the caller's own session — foreign,
|
|
792
|
+
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
793
|
+
* that run the store in-process may omit it.
|
|
794
|
+
*/
|
|
795
|
+
foreignLiveLease(sessionId, callerPid = process.pid) {
|
|
796
|
+
const live = this.leaseRow(sessionId);
|
|
797
|
+
return Boolean(live) && live.owner_pid !== callerPid;
|
|
798
|
+
}
|
|
764
799
|
verifyCredential(credential) {
|
|
765
800
|
assertId(credential.sessionId);
|
|
766
801
|
const row = this.leaseRow(credential.sessionId);
|
|
@@ -1033,13 +1068,51 @@ var SessionCatalogStore = class {
|
|
|
1033
1068
|
damaged: row.damaged !== 0
|
|
1034
1069
|
};
|
|
1035
1070
|
}
|
|
1036
|
-
listCatalog(
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
)
|
|
1071
|
+
listCatalog(criteria = {}) {
|
|
1072
|
+
const requestedLimit = criteria.limit ?? 100;
|
|
1073
|
+
if (!Number.isFinite(requestedLimit)) throw new TypeError("Invalid session catalog limit");
|
|
1074
|
+
const bounded = Math.min(MAX_PAGE, Math.max(1, Math.floor(requestedLimit)));
|
|
1075
|
+
const clauses = [];
|
|
1076
|
+
const values = [];
|
|
1077
|
+
const jsonText = (field) => `CASE WHEN json_valid(summary_json) THEN json_extract(summary_json,'$.${field}') END`;
|
|
1078
|
+
const literalLike = (value) => `%${value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
|
|
1079
|
+
const search = criteria.search?.trim();
|
|
1080
|
+
if (search) {
|
|
1081
|
+
const pattern = literalLike(search);
|
|
1082
|
+
clauses.push(
|
|
1083
|
+
`(session_id LIKE ? ESCAPE '\\' OR ${jsonText("title")} LIKE ? ESCAPE '\\' OR ${jsonText("name")} LIKE ? ESCAPE '\\')`
|
|
1084
|
+
);
|
|
1085
|
+
values.push(pattern, pattern, pattern);
|
|
1086
|
+
}
|
|
1087
|
+
if (criteria.since) {
|
|
1088
|
+
clauses.push(`${jsonText("startedAt")}>=?`);
|
|
1089
|
+
values.push(criteria.since);
|
|
1090
|
+
}
|
|
1091
|
+
if (criteria.until) {
|
|
1092
|
+
clauses.push(`${jsonText("startedAt")}<=?`);
|
|
1093
|
+
values.push(criteria.until);
|
|
1094
|
+
}
|
|
1095
|
+
if (criteria.provider) {
|
|
1096
|
+
clauses.push(`${jsonText("provider")}=?`);
|
|
1097
|
+
values.push(criteria.provider);
|
|
1098
|
+
}
|
|
1099
|
+
if (criteria.model) {
|
|
1100
|
+
clauses.push(`${jsonText("model")}=?`);
|
|
1101
|
+
values.push(criteria.model);
|
|
1102
|
+
}
|
|
1103
|
+
if (criteria.minTokens !== void 0) {
|
|
1104
|
+
if (!Number.isFinite(criteria.minTokens)) throw new TypeError("Invalid minimum token count");
|
|
1105
|
+
clauses.push(`CAST(COALESCE(${jsonText("tokenTotal")},0) AS REAL)>=?`);
|
|
1106
|
+
values.push(criteria.minTokens);
|
|
1107
|
+
}
|
|
1108
|
+
if (criteria.titleContains) {
|
|
1109
|
+
clauses.push(`${jsonText("title")} LIKE ? ESCAPE '\\'`);
|
|
1110
|
+
values.push(literalLike(criteria.titleContains.toLocaleLowerCase()));
|
|
1111
|
+
}
|
|
1112
|
+
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
1113
|
+
const rows = this.db.prepare(
|
|
1114
|
+
`SELECT * FROM sessions${where} ORDER BY COALESCE(${jsonText("lastActivityAt")},${jsonText("endedAt")},${jsonText("startedAt")}) DESC,${jsonText("startedAt")} DESC,session_id ASC LIMIT ?`
|
|
1115
|
+
).all(...values, bounded);
|
|
1043
1116
|
return rows.map((row) => this.catalogRecord(row));
|
|
1044
1117
|
}
|
|
1045
1118
|
getSummary(sessionId) {
|
|
@@ -1094,11 +1167,14 @@ var SessionCatalogStore = class {
|
|
|
1094
1167
|
throw error;
|
|
1095
1168
|
}
|
|
1096
1169
|
}
|
|
1097
|
-
acquireMaintenance(sessionId, operation, holderId, leaseMs) {
|
|
1170
|
+
acquireMaintenance(sessionId, operation, holderId, leaseMs, holderPid) {
|
|
1098
1171
|
assertId(sessionId);
|
|
1099
1172
|
return this.transaction(() => {
|
|
1100
1173
|
this.reapExpired();
|
|
1101
|
-
|
|
1174
|
+
const live = this.leaseRow(sessionId);
|
|
1175
|
+
if (live && (operation === "delete" || this.foreignLiveLease(sessionId, holderPid))) {
|
|
1176
|
+
throw conflict(`Session ${sessionId} is live`);
|
|
1177
|
+
}
|
|
1102
1178
|
const reservation = this.db.prepare(
|
|
1103
1179
|
"SELECT 1 AS yes FROM resume_reservations WHERE target_session_id=? AND expires_at>?"
|
|
1104
1180
|
).get(sessionId, Date.now());
|
|
@@ -1191,7 +1267,7 @@ var SessionCatalogStore = class {
|
|
|
1191
1267
|
rebuildCatalog() {
|
|
1192
1268
|
const summaries = this.walkFiles(this.sessionsDir, ".summary.json");
|
|
1193
1269
|
const transcripts = this.walkFiles(this.sessionsDir, ".jsonl").filter(
|
|
1194
|
-
(file) =>
|
|
1270
|
+
(file) => isSessionTranscriptFileName(path2.basename(file))
|
|
1195
1271
|
);
|
|
1196
1272
|
const ids = /* @__PURE__ */ new Set();
|
|
1197
1273
|
for (const file of [...summaries, ...transcripts]) {
|
|
@@ -1400,11 +1476,20 @@ var OPERATION_KEYS = {
|
|
|
1400
1476
|
subscribe: ["cursor"],
|
|
1401
1477
|
unsubscribe: [],
|
|
1402
1478
|
upsert_summary: ["summary", "transcriptRelativePath", "summaryRelativePath"],
|
|
1403
|
-
list_catalog: [
|
|
1479
|
+
list_catalog: [
|
|
1480
|
+
"limit",
|
|
1481
|
+
"search",
|
|
1482
|
+
"since",
|
|
1483
|
+
"until",
|
|
1484
|
+
"provider",
|
|
1485
|
+
"model",
|
|
1486
|
+
"minTokens",
|
|
1487
|
+
"titleContains"
|
|
1488
|
+
],
|
|
1404
1489
|
resolve_id: ["query"],
|
|
1405
1490
|
get_summary: ["sessionId"],
|
|
1406
1491
|
rename: ["sessionId", "name"],
|
|
1407
|
-
acquire_maintenance: ["sessionId", "operation", "holderId", "leaseMs"],
|
|
1492
|
+
acquire_maintenance: ["sessionId", "operation", "holderId", "leaseMs", "holderPid"],
|
|
1408
1493
|
release_maintenance: ["lease"],
|
|
1409
1494
|
delete: ["sessionId", "lease"],
|
|
1410
1495
|
prune: ["maxAgeDays", "holderId"],
|
|
@@ -1558,10 +1643,7 @@ async function dispatch(op, args) {
|
|
|
1558
1643
|
}
|
|
1559
1644
|
case "list_catalog": {
|
|
1560
1645
|
const value = args;
|
|
1561
|
-
return catalog.listCatalog(
|
|
1562
|
-
value.limit,
|
|
1563
|
-
value.search
|
|
1564
|
-
);
|
|
1646
|
+
return catalog.listCatalog(value);
|
|
1565
1647
|
}
|
|
1566
1648
|
case "resolve_id":
|
|
1567
1649
|
return catalog.resolveId(
|
|
@@ -1584,7 +1666,8 @@ async function dispatch(op, args) {
|
|
|
1584
1666
|
value.sessionId,
|
|
1585
1667
|
value.operation,
|
|
1586
1668
|
value.holderId,
|
|
1587
|
-
value.leaseMs
|
|
1669
|
+
value.leaseMs,
|
|
1670
|
+
value.holderPid
|
|
1588
1671
|
);
|
|
1589
1672
|
}
|
|
1590
1673
|
case "release_maintenance": {
|
|
@@ -67,6 +67,16 @@ export interface CatalogSessionRecord extends SessionSummary {
|
|
|
67
67
|
indexedAt: string;
|
|
68
68
|
damaged: boolean;
|
|
69
69
|
}
|
|
70
|
+
export interface SessionCatalogListArgs {
|
|
71
|
+
limit?: number | undefined;
|
|
72
|
+
search?: string | undefined;
|
|
73
|
+
since?: string | undefined;
|
|
74
|
+
until?: string | undefined;
|
|
75
|
+
provider?: string | undefined;
|
|
76
|
+
model?: string | undefined;
|
|
77
|
+
minTokens?: number | undefined;
|
|
78
|
+
titleContains?: string | undefined;
|
|
79
|
+
}
|
|
70
80
|
export type SessionCatalogEventKind = 'session.claimed' | 'session.presence_changed' | 'session.closing' | 'session.released' | 'session.catalog_changed' | 'session.deleted' | 'session.rebuild_started' | 'session.rebuild_completed';
|
|
71
81
|
export interface SessionCatalogEvent {
|
|
72
82
|
instanceId: string;
|
|
@@ -76,6 +86,12 @@ export interface SessionCatalogEvent {
|
|
|
76
86
|
generation: number;
|
|
77
87
|
at: string;
|
|
78
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* NOTE: adding a field to any `args` below also requires adding it to
|
|
91
|
+
* `OPERATION_KEYS` in `project-server.ts` — the daemon validates every
|
|
92
|
+
* request against that allowlist and rejects unknown fields at runtime,
|
|
93
|
+
* which TypeScript cannot catch.
|
|
94
|
+
*/
|
|
79
95
|
export interface SessionCatalogOperations {
|
|
80
96
|
ping: {
|
|
81
97
|
args: Record<string, never>;
|
|
@@ -179,10 +195,7 @@ export interface SessionCatalogOperations {
|
|
|
179
195
|
result: CatalogSessionRecord;
|
|
180
196
|
};
|
|
181
197
|
list_catalog: {
|
|
182
|
-
args:
|
|
183
|
-
limit?: number;
|
|
184
|
-
search?: string;
|
|
185
|
-
};
|
|
198
|
+
args: SessionCatalogListArgs;
|
|
186
199
|
result: CatalogSessionRecord[];
|
|
187
200
|
};
|
|
188
201
|
resolve_id: {
|
|
@@ -210,6 +223,9 @@ export interface SessionCatalogOperations {
|
|
|
210
223
|
operation: MaintenanceLease['operation'];
|
|
211
224
|
holderId: string;
|
|
212
225
|
leaseMs?: number;
|
|
226
|
+
/** Caller's OS pid. The server runs in the detached catalog daemon, so
|
|
227
|
+
* it cannot use its own `process.pid` to recognise the session owner. */
|
|
228
|
+
holderPid?: number;
|
|
213
229
|
};
|
|
214
230
|
result: MaintenanceLease;
|
|
215
231
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionRegistryEntry } from '../session-registry-types.js';
|
|
2
2
|
import type { SessionSummary } from '../types/session.js';
|
|
3
|
-
import type { CatalogSessionRecord, MaintenanceLease, ResumeReservation, SessionCatalogHealth, SessionLeaseCredential } from './protocol.js';
|
|
3
|
+
import type { CatalogSessionRecord, MaintenanceLease, ResumeReservation, SessionCatalogHealth, SessionCatalogListArgs, SessionLeaseCredential } from './protocol.js';
|
|
4
4
|
export declare class SessionCatalogStore {
|
|
5
5
|
readonly projectDir: string;
|
|
6
6
|
readonly databasePath: string;
|
|
@@ -17,6 +17,22 @@ export declare class SessionCatalogStore {
|
|
|
17
17
|
private reapExpired;
|
|
18
18
|
private maintenanceExists;
|
|
19
19
|
private leaseRow;
|
|
20
|
+
/**
|
|
21
|
+
* True when `sessionId` has a live `session_leases` row owned by a
|
|
22
|
+
* different OS process than the *caller*. Used by
|
|
23
|
+
* `acquireMaintenance` to distinguish "another running wstack" (which
|
|
24
|
+
* must be fenced off) from "this very TUI owning its own session"
|
|
25
|
+
* (which is allowed to claim a non-destructive maintenance lease on
|
|
26
|
+
* its own transcript).
|
|
27
|
+
*
|
|
28
|
+
* `callerPid` must be the pid of the process that asked for the lease,
|
|
29
|
+
* NOT `process.pid`. In the built runtime this store lives inside the
|
|
30
|
+
* detached project-catalog daemon, so `process.pid` is the daemon's and
|
|
31
|
+
* would mark every lease — including the caller's own session — foreign,
|
|
32
|
+
* making `/clear` and `/rewind` fail with "Session … is live". Callers
|
|
33
|
+
* that run the store in-process may omit it.
|
|
34
|
+
*/
|
|
35
|
+
private foreignLiveLease;
|
|
20
36
|
private verifyCredential;
|
|
21
37
|
private createLease;
|
|
22
38
|
claimNew(entry: SessionRegistryEntry, ownerInstanceId: string, leaseMs?: number): SessionLeaseCredential;
|
|
@@ -36,11 +52,11 @@ export declare class SessionCatalogStore {
|
|
|
36
52
|
private containedPath;
|
|
37
53
|
upsertSummary(summary: SessionSummary, transcriptRelativePath?: string, summaryRelativePath?: string): CatalogSessionRecord;
|
|
38
54
|
private catalogRecord;
|
|
39
|
-
listCatalog(
|
|
55
|
+
listCatalog(criteria?: SessionCatalogListArgs): CatalogSessionRecord[];
|
|
40
56
|
getSummary(sessionId: string): CatalogSessionRecord | null;
|
|
41
57
|
resolveId(query: string): string;
|
|
42
58
|
rename(sessionId: string, name: string): Promise<CatalogSessionRecord>;
|
|
43
|
-
acquireMaintenance(sessionId: string, operation: MaintenanceLease['operation'], holderId: string, leaseMs?: number): MaintenanceLease;
|
|
59
|
+
acquireMaintenance(sessionId: string, operation: MaintenanceLease['operation'], holderId: string, leaseMs?: number, holderPid?: number): MaintenanceLease;
|
|
44
60
|
releaseMaintenance(lease: MaintenanceLease): void;
|
|
45
61
|
delete(sessionId: string, lease: MaintenanceLease): void;
|
|
46
62
|
prune(maxAgeDays: number, holderId: string): number;
|