@papi-ai/server 0.7.80 → 0.7.82
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/backfill-cycle-metrics.js +299 -2
- package/dist/index.js +1250 -139
- package/dist/prompts.js +36 -6
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ __export(git_exports, {
|
|
|
28
28
|
ensureTagAtHead: () => ensureTagAtHead,
|
|
29
29
|
findTaskCommitsOnBase: () => findTaskCommitsOnBase,
|
|
30
30
|
getBranchDiff: () => getBranchDiff,
|
|
31
|
+
getCommitFiles: () => getCommitFiles,
|
|
31
32
|
getCommitsSinceTag: () => getCommitsSinceTag,
|
|
32
33
|
getCurrentBranch: () => getCurrentBranch,
|
|
33
34
|
getDocPathsTouchedOnBranch: () => getDocPathsTouchedOnBranch,
|
|
@@ -41,11 +42,13 @@ __export(git_exports, {
|
|
|
41
42
|
getModifiedFiles: () => getModifiedFiles,
|
|
42
43
|
getOriginRepoSlug: () => getOriginRepoSlug,
|
|
43
44
|
getOriginUrl: () => getOriginUrl,
|
|
45
|
+
getPathsDifferingFrom: () => getPathsDifferingFrom,
|
|
44
46
|
getPullRequestUrl: () => getPullRequestUrl,
|
|
45
47
|
getRemoteBranchFiles: () => getRemoteBranchFiles,
|
|
46
48
|
getRootCommitHash: () => getRootCommitHash,
|
|
47
49
|
getStagedFiles: () => getStagedFiles,
|
|
48
50
|
getTagTarget: () => getTagTarget,
|
|
51
|
+
getTaskDiff: () => getTaskDiff,
|
|
49
52
|
getTaskIdsOnBranch: () => getTaskIdsOnBranch,
|
|
50
53
|
getTrackedModifiedFiles: () => getTrackedModifiedFiles,
|
|
51
54
|
getUnmergedBranches: () => getUnmergedBranches,
|
|
@@ -60,6 +63,7 @@ __export(git_exports, {
|
|
|
60
63
|
isGhAvailable: () => isGhAvailable,
|
|
61
64
|
isGitAvailable: () => isGitAvailable,
|
|
62
65
|
isGitRepo: () => isGitRepo,
|
|
66
|
+
isPathCommittedAnywhere: () => isPathCommittedAnywhere,
|
|
63
67
|
isPathIgnored: () => isPathIgnored,
|
|
64
68
|
isPathTracked: () => isPathTracked,
|
|
65
69
|
listGroupedCycleBranches: () => listGroupedCycleBranches,
|
|
@@ -99,6 +103,22 @@ function isGitRepo(cwd) {
|
|
|
99
103
|
return false;
|
|
100
104
|
}
|
|
101
105
|
}
|
|
106
|
+
function isPathCommittedAnywhere(cwd, path3) {
|
|
107
|
+
const run = (args) => {
|
|
108
|
+
try {
|
|
109
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const commit = run(["log", "--all", "--format=%H", "-1", "--", path3]);
|
|
115
|
+
if (!commit) return false;
|
|
116
|
+
const committedBlob = run(["rev-parse", `${commit}:${path3}`]);
|
|
117
|
+
if (!committedBlob) return false;
|
|
118
|
+
const onDiskBlob = run(["hash-object", "--", path3]);
|
|
119
|
+
if (!onDiskBlob) return false;
|
|
120
|
+
return committedBlob === onDiskBlob;
|
|
121
|
+
}
|
|
102
122
|
function isPathTracked(cwd, path3) {
|
|
103
123
|
try {
|
|
104
124
|
execFileSync("git", ["ls-files", "--error-unmatch", "--", path3], {
|
|
@@ -271,6 +291,50 @@ function getBranchDiff(cwd, base = "origin/main", maxBytes = 2e5) {
|
|
|
271
291
|
}
|
|
272
292
|
return "";
|
|
273
293
|
}
|
|
294
|
+
function getTaskDiff(cwd, taskId, base = "origin/main", maxBytes = 2e5) {
|
|
295
|
+
const truncate = (out) => out.length > maxBytes ? `${out.slice(0, maxBytes)}
|
|
296
|
+
|
|
297
|
+
... [diff truncated at ${Math.round(maxBytes / 1024)} KB]` : out;
|
|
298
|
+
try {
|
|
299
|
+
const shas = execFileSync(
|
|
300
|
+
"git",
|
|
301
|
+
["log", "--all", "--format=%H", `--grep=${taskId})`, "--fixed-strings"],
|
|
302
|
+
{ cwd, encoding: "utf-8", maxBuffer: 8 * 1024 * 1024 }
|
|
303
|
+
).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
304
|
+
if (shas.length > 0) {
|
|
305
|
+
const parts = [];
|
|
306
|
+
for (const sha of [...shas].reverse()) {
|
|
307
|
+
try {
|
|
308
|
+
const one = execFileSync("git", ["diff", `${sha}^..${sha}`], {
|
|
309
|
+
cwd,
|
|
310
|
+
encoding: "utf-8",
|
|
311
|
+
maxBuffer: 32 * 1024 * 1024
|
|
312
|
+
});
|
|
313
|
+
if (one) parts.push(one);
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const out = parts.join("\n");
|
|
318
|
+
if (out) {
|
|
319
|
+
const range = shas.length === 1 ? shas[0].slice(0, 8) : `${shas[shas.length - 1].slice(0, 8)}\u2026${shas[0].slice(0, 8)}`;
|
|
320
|
+
return {
|
|
321
|
+
diff: truncate(out),
|
|
322
|
+
scope: "task-commits",
|
|
323
|
+
detail: `${shas.length} commit(s) for ${taskId} (${range}), each against its own parent`
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
const branch = getCurrentBranch(cwd);
|
|
330
|
+
const diff = getBranchDiff(cwd, base, maxBytes);
|
|
331
|
+
if (!diff) return { diff: "", scope: "none", detail: "no diff resolved" };
|
|
332
|
+
return {
|
|
333
|
+
diff,
|
|
334
|
+
scope: "whole-branch",
|
|
335
|
+
detail: `no commit naming ${taskId} was found, so this is the ENTIRE diff of ${branch ?? "the current branch"} vs ${base} \u2014 it may include other tasks' work, and may not include this task's`
|
|
336
|
+
};
|
|
337
|
+
}
|
|
274
338
|
function getHeadCommitSubject(cwd) {
|
|
275
339
|
try {
|
|
276
340
|
const out = execFileSync("git", ["log", "-1", "--format=%s"], {
|
|
@@ -318,6 +382,28 @@ function branchExists(cwd, branch) {
|
|
|
318
382
|
return false;
|
|
319
383
|
}
|
|
320
384
|
}
|
|
385
|
+
function getPathsDifferingFrom(cwd, target) {
|
|
386
|
+
try {
|
|
387
|
+
const out = execFileSync("git", ["diff", "--name-only", "HEAD", target], {
|
|
388
|
+
cwd,
|
|
389
|
+
encoding: "utf-8"
|
|
390
|
+
});
|
|
391
|
+
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
392
|
+
} catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function getCommitFiles(cwd, ref = "HEAD") {
|
|
397
|
+
try {
|
|
398
|
+
const out = execFileSync("git", ["show", "--name-only", "--pretty=format:", ref], {
|
|
399
|
+
cwd,
|
|
400
|
+
encoding: "utf-8"
|
|
401
|
+
});
|
|
402
|
+
return out.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
321
407
|
function checkoutBranch(cwd, branch) {
|
|
322
408
|
try {
|
|
323
409
|
execFileSync("git", ["checkout", branch], { cwd, encoding: "utf-8" });
|
|
@@ -1216,7 +1302,7 @@ function wrapWithForwarding(instance) {
|
|
|
1216
1302
|
function createProxyAdapter(config) {
|
|
1217
1303
|
return wrapWithForwarding(new ProxyPapiAdapter(config));
|
|
1218
1304
|
}
|
|
1219
|
-
var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, NO_FORWARD, ProxyPapiAdapter;
|
|
1305
|
+
var JSONB_PASSTHROUGH_KEYS, DISPLAY_ID_METHODS, HOSTED_SERVED, NO_FORWARD, ProxyPapiAdapter;
|
|
1220
1306
|
var init_proxy_adapter = __esm({
|
|
1221
1307
|
"src/proxy-adapter.ts"() {
|
|
1222
1308
|
"use strict";
|
|
@@ -1244,6 +1330,156 @@ var init_proxy_adapter = __esm({
|
|
|
1244
1330
|
"getRecentReviews",
|
|
1245
1331
|
"getActiveDecisions"
|
|
1246
1332
|
]);
|
|
1333
|
+
HOSTED_SERVED = /* @__PURE__ */ new Set([
|
|
1334
|
+
"actionRecommendation",
|
|
1335
|
+
"addAgendaTopic",
|
|
1336
|
+
"addContributorByEmail",
|
|
1337
|
+
"appendBuildReport",
|
|
1338
|
+
"appendCycleLearnings",
|
|
1339
|
+
"appendCycleMetrics",
|
|
1340
|
+
"appendDecisionEvent",
|
|
1341
|
+
"appendToolMetric",
|
|
1342
|
+
"archiveTasks",
|
|
1343
|
+
"claimTask",
|
|
1344
|
+
"clearPendingReviewResponse",
|
|
1345
|
+
"compressBuildReports",
|
|
1346
|
+
"compressCycleLog",
|
|
1347
|
+
"confirmPendingActiveDecisions",
|
|
1348
|
+
"correctLatestBuildReportEffort",
|
|
1349
|
+
"countDueOwnerActions",
|
|
1350
|
+
"countNudgedOwnerActions",
|
|
1351
|
+
"countOpenOwnerActions",
|
|
1352
|
+
"countOwnerActionsBlockingTasks",
|
|
1353
|
+
"countPlanRunsForCycle",
|
|
1354
|
+
"createConvention",
|
|
1355
|
+
"createCycle",
|
|
1356
|
+
"createHorizon",
|
|
1357
|
+
"createOwnerAction",
|
|
1358
|
+
"createStage",
|
|
1359
|
+
"createTask",
|
|
1360
|
+
"deleteActiveDecision",
|
|
1361
|
+
"deleteConvention",
|
|
1362
|
+
"deleteDoc",
|
|
1363
|
+
"dismissRecommendation",
|
|
1364
|
+
"findPendingDocActionsForTask",
|
|
1365
|
+
"getActiveDecisions",
|
|
1366
|
+
"getActiveStage",
|
|
1367
|
+
"getBuildReportCountForTask",
|
|
1368
|
+
"getBuildReportsSince",
|
|
1369
|
+
"getContextHashes",
|
|
1370
|
+
"getContextUtilisation",
|
|
1371
|
+
"getCostSnapshots",
|
|
1372
|
+
"getCostSummary",
|
|
1373
|
+
"getCurrentNorthStar",
|
|
1374
|
+
"getCycleHealth",
|
|
1375
|
+
"getCycleLearningPatterns",
|
|
1376
|
+
"getCycleLearnings",
|
|
1377
|
+
"getCycleLog",
|
|
1378
|
+
"getCycleLogSince",
|
|
1379
|
+
"getDecisionEvents",
|
|
1380
|
+
"getDecisionEventsSince",
|
|
1381
|
+
"getDecisionScorePatterns",
|
|
1382
|
+
"getDecisionScores",
|
|
1383
|
+
"getDecisionUsage",
|
|
1384
|
+
"getDoc",
|
|
1385
|
+
"getDocBody",
|
|
1386
|
+
"getDocBodyUsage",
|
|
1387
|
+
"getDogfoodLog",
|
|
1388
|
+
"getEstimationCalibration",
|
|
1389
|
+
"getFailedBuildAttemptsForTask",
|
|
1390
|
+
"getHarnessInventory",
|
|
1391
|
+
"getHarnessState",
|
|
1392
|
+
"getLastStrategyReviewCycle",
|
|
1393
|
+
"getLatestDecisionScores",
|
|
1394
|
+
"getModelOutcomeStats",
|
|
1395
|
+
"getModuleEstimationStats",
|
|
1396
|
+
"getNorthStarSetCycle",
|
|
1397
|
+
"getNorthStarStaleness",
|
|
1398
|
+
"getOwnerIdentity",
|
|
1399
|
+
"getPendingAgendaTopics",
|
|
1400
|
+
"getPendingRecommendations",
|
|
1401
|
+
"getPendingReviewResponse",
|
|
1402
|
+
"getPlanContextSummary",
|
|
1403
|
+
"getProjectInfo",
|
|
1404
|
+
"getProjectOwnerUserId",
|
|
1405
|
+
"getRecentBuildReports",
|
|
1406
|
+
"getRecentReviews",
|
|
1407
|
+
"getRecentTaskComments",
|
|
1408
|
+
"getRecommendationEffectiveness",
|
|
1409
|
+
"getStrategyReviews",
|
|
1410
|
+
"getTask",
|
|
1411
|
+
"getTasks",
|
|
1412
|
+
"getToolCallCount",
|
|
1413
|
+
"getUnactionedDogfoodEntries",
|
|
1414
|
+
"getUnnotifiedResolvedFeedback",
|
|
1415
|
+
"hasToolMilestone",
|
|
1416
|
+
"insertPlanRun",
|
|
1417
|
+
"insertToolRun",
|
|
1418
|
+
"linkOwnerActionToTask",
|
|
1419
|
+
"linkPhasesToStage",
|
|
1420
|
+
"listContributors",
|
|
1421
|
+
"listConventions",
|
|
1422
|
+
"listMyBugReports",
|
|
1423
|
+
"listOwnerActionsForBlockerScan",
|
|
1424
|
+
"logEntityReferences",
|
|
1425
|
+
"markAgendaTopicsAddressed",
|
|
1426
|
+
"markCycleLearningResolved",
|
|
1427
|
+
"markFeedbackNotified",
|
|
1428
|
+
"moveTask",
|
|
1429
|
+
"planWriteBack",
|
|
1430
|
+
"projectExists",
|
|
1431
|
+
"queryBoard",
|
|
1432
|
+
"readCycleMetrics",
|
|
1433
|
+
"readCycles",
|
|
1434
|
+
"readCycles",
|
|
1435
|
+
"readDiscoveryCanvas",
|
|
1436
|
+
"readHorizons",
|
|
1437
|
+
"readPhases",
|
|
1438
|
+
"readPlanningLog",
|
|
1439
|
+
"readProductBrief",
|
|
1440
|
+
"readRegistries",
|
|
1441
|
+
"readStages",
|
|
1442
|
+
"readToolMetrics",
|
|
1443
|
+
"recordProgressStep",
|
|
1444
|
+
"recordTransition",
|
|
1445
|
+
"registerDoc",
|
|
1446
|
+
"removeContributorByEmail",
|
|
1447
|
+
"reorderDocs",
|
|
1448
|
+
"replaceHarnessInventory",
|
|
1449
|
+
"resolveLearningsForDoneTasks",
|
|
1450
|
+
"savePendingReviewResponse",
|
|
1451
|
+
"searchDocs",
|
|
1452
|
+
"setCriterionMet",
|
|
1453
|
+
"setCycleHealth",
|
|
1454
|
+
"setHarnessState",
|
|
1455
|
+
"setProjectPapiDir",
|
|
1456
|
+
"storeDocBody",
|
|
1457
|
+
"submitBugReport",
|
|
1458
|
+
"unclaimTask",
|
|
1459
|
+
"updateActiveDecision",
|
|
1460
|
+
"updateCycleLearningActionRef",
|
|
1461
|
+
"updateDiscoveryCanvas",
|
|
1462
|
+
"updateDocAction",
|
|
1463
|
+
"updateDocStatus",
|
|
1464
|
+
"updateDogfoodEntryStatus",
|
|
1465
|
+
"updateHorizonStatus",
|
|
1466
|
+
"updatePhaseStatus",
|
|
1467
|
+
"updateProductBrief",
|
|
1468
|
+
"updateRegistries",
|
|
1469
|
+
"updateStageExitCriteria",
|
|
1470
|
+
"updateStageStatus",
|
|
1471
|
+
"updateTask",
|
|
1472
|
+
"updateTaskStatus",
|
|
1473
|
+
"upsertActiveDecision",
|
|
1474
|
+
"upsertNorthStar",
|
|
1475
|
+
"writeCycleLogEntry",
|
|
1476
|
+
"writeDecisionScore",
|
|
1477
|
+
"writeDogfoodEntries",
|
|
1478
|
+
"writePhases",
|
|
1479
|
+
"writeRecommendation",
|
|
1480
|
+
"writeReview",
|
|
1481
|
+
"writeStrategyReview"
|
|
1482
|
+
]);
|
|
1247
1483
|
NO_FORWARD = /* @__PURE__ */ new Set([
|
|
1248
1484
|
// (1) local-only
|
|
1249
1485
|
"close",
|
|
@@ -1272,7 +1508,7 @@ var init_proxy_adapter = __esm({
|
|
|
1272
1508
|
"listContributorReleasePrs",
|
|
1273
1509
|
"claimReview",
|
|
1274
1510
|
"getSiblingAds",
|
|
1275
|
-
"getSiblingRepoTasks"
|
|
1511
|
+
"getSiblingRepoTasks",
|
|
1276
1512
|
// task-2828 (C339): attributed-intelligence analytics reader — pg-only that cycle.
|
|
1277
1513
|
// task-2864 (C343): WIRED. getModelOutcomeStats now has an edge case handler (raw
|
|
1278
1514
|
// SQL via postgres.js mirroring the pg query + inlined computeModelOutcomes bucketing)
|
|
@@ -1296,6 +1532,28 @@ var init_proxy_adapter = __esm({
|
|
|
1296
1532
|
// (case handler + ALLOWED_METHODS/WRITE_METHODS entries), so it forwards for
|
|
1297
1533
|
// hosted callers and persists a project-scoped cycle_progress_steps row. Removed
|
|
1298
1534
|
// from NO_FORWARD (was the task-2484 pg-only gap) — hosted parity restored.
|
|
1535
|
+
//
|
|
1536
|
+
// (3) pg-only optimisations probed STRUCTURALLY with a local fallback beside them.
|
|
1537
|
+
// These are the dangerous class: the caller asks `typeof adapter.X === 'function'`
|
|
1538
|
+
// to decide whether the fast path exists, and the get-trap answers "yes" for any
|
|
1539
|
+
// name absent from this set. The probe then passes, the call forwards, and the
|
|
1540
|
+
// edge 403s — while a working fallback sits a few lines below, unreachable.
|
|
1541
|
+
// A method belongs here when BOTH are true: it is probed by `typeof` rather than
|
|
1542
|
+
// called unconditionally, and the probe's else-branch is a real fallback.
|
|
1543
|
+
//
|
|
1544
|
+
// task-3290 (C361): allocateActiveDecision + applyActiveDecisionUpdates are pg-only
|
|
1545
|
+
// (atomic id allocation; transactional batch apply). Both are probed in
|
|
1546
|
+
// services/strategy.ts — asDecisionIdAllocator and asDecisionBatchApplier — and both
|
|
1547
|
+
// have read-then-write / sequential fallbacks. Hosted users hit a 403 minting ANY
|
|
1548
|
+
// Active Decision via setup's AD seed or strategy_change until these were listed.
|
|
1549
|
+
"allocateActiveDecision",
|
|
1550
|
+
"applyActiveDecisionUpdates",
|
|
1551
|
+
// task-3290 (C361), same sweep: getLastZoomOutCycle was documented in the parity
|
|
1552
|
+
// ledger as "proxy NO_FORWARD → safe no-op" while NOT being in this set. It is
|
|
1553
|
+
// optional-chain probed in services/health.ts and wrapped in try/catch, so it
|
|
1554
|
+
// degraded rather than crashed — but every hosted `orient` paid a round-trip to
|
|
1555
|
+
// earn a silent 403. Listing it makes the ledger's claim true and skips the trip.
|
|
1556
|
+
"getLastZoomOutCycle"
|
|
1299
1557
|
]);
|
|
1300
1558
|
ProxyPapiAdapter = class _ProxyPapiAdapter {
|
|
1301
1559
|
endpoint;
|
|
@@ -1308,6 +1566,30 @@ var init_proxy_adapter = __esm({
|
|
|
1308
1566
|
this.projectId = config.projectId ?? "";
|
|
1309
1567
|
this.onAuthRejected = config.onAuthRejected;
|
|
1310
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Does the hosted path genuinely serve `name`? (task-3022, C362)
|
|
1571
|
+
*
|
|
1572
|
+
* This is the honest answer to the question `typeof adapter.name === 'function'`
|
|
1573
|
+
* USED to ask and could not answer: the get-trap below manufactures a function
|
|
1574
|
+
* for any name absent from NO_FORWARD, so a structural probe reads true for
|
|
1575
|
+
* pg-only methods and the caller then earns a 403 from the edge.
|
|
1576
|
+
*
|
|
1577
|
+
* Answered from NO_FORWARD itself, deliberately — the same set the forwarder
|
|
1578
|
+
* consults — so this can never disagree with what an actual call would do. A
|
|
1579
|
+
* name in NO_FORWARD is either local-only or not yet wired hosted; either way
|
|
1580
|
+
* the caller must take its fallback. The proxy-parity test guarantees every
|
|
1581
|
+
* method is an explicit wrapper, in NO_FORWARD, or in the edge allowlist, so
|
|
1582
|
+
* "not in NO_FORWARD" is a sound proxy for "served hosted".
|
|
1583
|
+
*
|
|
1584
|
+
* A REAL method, not a forwarded one: Reflect.get in the trap returns it before
|
|
1585
|
+
* the forwarding branch is reached, so callers get this implementation rather
|
|
1586
|
+
* than a forwarder that would POST "supportsMethod" to the edge.
|
|
1587
|
+
*/
|
|
1588
|
+
supportsMethod(name) {
|
|
1589
|
+
if (NO_FORWARD.has(name)) return false;
|
|
1590
|
+
if (name in _ProxyPapiAdapter.prototype) return true;
|
|
1591
|
+
return HOSTED_SERVED.has(name);
|
|
1592
|
+
}
|
|
1311
1593
|
/**
|
|
1312
1594
|
* task-1773: bearer-only auth probe. Hits the USER-scoped `project-list` route
|
|
1313
1595
|
* (no projectId needed), so it answers exactly one question: does the proxy
|
|
@@ -1827,6 +2109,21 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
|
|
|
1827
2109
|
updateDogfoodEntryStatus(id, status, linkedTaskId) {
|
|
1828
2110
|
return this.invoke("updateDogfoodEntryStatus", [id, status, linkedTaskId]);
|
|
1829
2111
|
}
|
|
2112
|
+
// --- Project conventions (task-3271) ---
|
|
2113
|
+
//
|
|
2114
|
+
// Wired to the edge handler from the start rather than parked in NO_FORWARD.
|
|
2115
|
+
// The hosted remote connector is the only install path an external user has,
|
|
2116
|
+
// so a local-only conventions store would be a feature nobody in the actual
|
|
2117
|
+
// user base can reach.
|
|
2118
|
+
listConventions() {
|
|
2119
|
+
return this.invoke("listConventions");
|
|
2120
|
+
}
|
|
2121
|
+
createConvention(convention) {
|
|
2122
|
+
return this.invoke("createConvention", [convention]);
|
|
2123
|
+
}
|
|
2124
|
+
deleteConvention(id) {
|
|
2125
|
+
return this.invoke("deleteConvention", [id]);
|
|
2126
|
+
}
|
|
1830
2127
|
// --- Harness inventory (task-1896) ---
|
|
1831
2128
|
getHarnessInventory() {
|
|
1832
2129
|
return this.invoke("getHarnessInventory");
|