farai 0.2.8 → 0.2.9
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/cli/index.js +669 -137
- package/dist/cli/index.js.map +12 -12
- package/package.json +1 -2
- package/src/agent-skills/library/binary-exploitation/SKILL.md +0 -26
- package/src/agent-skills/library/binary-reversing/SKILL.md +0 -24
- package/src/agent-skills/library/crypto-solving/SKILL.md +0 -24
- package/src/agent-skills/library/ctf-solving/SKILL.md +0 -27
- package/src/agent-skills/library/digital-forensics/SKILL.md +0 -24
- package/src/agent-skills/library/ffuf/SKILL.md +0 -23
- package/src/agent-skills/library/nmap/SKILL.md +0 -30
- package/src/agent-skills/library/packet-analysis/SKILL.md +0 -24
- package/src/agent-skills/library/payload-protocol-crafting/SKILL.md +0 -26
- package/src/agent-skills/library/privilege-escalation/SKILL.md +0 -24
- package/src/agent-skills/library/reverse-shells/SKILL.md +0 -54
- package/src/agent-skills/library/searchsploit/SKILL.md +0 -18
- package/src/agent-skills/library/source-security-review/SKILL.md +0 -25
- package/src/agent-skills/library/web-assessment/SKILL.md +0 -28
package/dist/cli/index.js
CHANGED
|
@@ -5707,33 +5707,67 @@ var init_docker_environment = __esm(() => {
|
|
|
5707
5707
|
|
|
5708
5708
|
// src/agent-container/lifecycle.ts
|
|
5709
5709
|
import { createHash as createHash3 } from "crypto";
|
|
5710
|
+
import { statSync as statSync2 } from "fs";
|
|
5710
5711
|
import { join as join8, resolve as resolve3 } from "path";
|
|
5711
5712
|
import { Database as Database2 } from "bun:sqlite";
|
|
5712
5713
|
|
|
5713
5714
|
class DockerContainerLifecycle {
|
|
5714
5715
|
acquired = new Map;
|
|
5716
|
+
stopFailures = new Set;
|
|
5717
|
+
activeOperations = 0;
|
|
5715
5718
|
lastReconcileAt = 0;
|
|
5716
5719
|
disposed = false;
|
|
5720
|
+
suspending = false;
|
|
5717
5721
|
constructor(runtimeId, runner = runDockerProcess, options = {}) {
|
|
5718
5722
|
this.runtimeId = runtimeId;
|
|
5719
5723
|
this.runner = runner;
|
|
5720
5724
|
this.registry = new ContainerLeaseRegistry(options.registryPath);
|
|
5721
5725
|
this.idleTtlMs = options.idleTtlMs ?? CONTAINER_IDLE_TTL_MS;
|
|
5726
|
+
this.reconcileTimer = setInterval(() => {
|
|
5727
|
+
if (this.disposed || this.suspending)
|
|
5728
|
+
return;
|
|
5729
|
+
this.reconcile().catch(() => {
|
|
5730
|
+
return;
|
|
5731
|
+
});
|
|
5732
|
+
}, CONTAINER_RECONCILE_INTERVAL_MS);
|
|
5733
|
+
this.reconcileTimer.unref?.();
|
|
5722
5734
|
}
|
|
5723
5735
|
async acquire(identity) {
|
|
5724
5736
|
this.assertOpen();
|
|
5725
|
-
|
|
5737
|
+
this.beginOperation();
|
|
5738
|
+
try {
|
|
5739
|
+
await this.acquireUnlocked(identity);
|
|
5740
|
+
} finally {
|
|
5741
|
+
this.endOperation();
|
|
5742
|
+
}
|
|
5743
|
+
}
|
|
5744
|
+
async withLease(identity, operation) {
|
|
5745
|
+
this.assertOpen();
|
|
5746
|
+
this.beginOperation();
|
|
5747
|
+
try {
|
|
5748
|
+
await this.acquireUnlocked(identity);
|
|
5749
|
+
this.assertOpen();
|
|
5750
|
+
return await operation();
|
|
5751
|
+
} finally {
|
|
5752
|
+
this.endOperation();
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
async acquireUnlocked(identity) {
|
|
5756
|
+
if (Date.now() - this.lastReconcileAt >= CONTAINER_RECONCILE_INTERVAL_MS)
|
|
5726
5757
|
await this.reconcile();
|
|
5727
5758
|
else
|
|
5728
5759
|
await this.reconcilePromise;
|
|
5760
|
+
this.assertOpen();
|
|
5729
5761
|
this.registry.acquire(identity, this.runtimeId, CONTAINER_LEASE_MS);
|
|
5730
5762
|
this.acquired.set(identity.containerName, identity);
|
|
5763
|
+
this.stopFailures.delete(identity.containerName);
|
|
5731
5764
|
}
|
|
5732
5765
|
release(identity) {
|
|
5733
5766
|
if (this.disposed)
|
|
5734
5767
|
return;
|
|
5735
5768
|
this.registry.release(identity.containerName, this.runtimeId);
|
|
5736
5769
|
this.acquired.delete(identity.containerName);
|
|
5770
|
+
this.stopFailures.delete(identity.containerName);
|
|
5737
5771
|
}
|
|
5738
5772
|
renew() {
|
|
5739
5773
|
if (this.disposed || this.acquired.size === 0)
|
|
@@ -5744,8 +5778,10 @@ class DockerContainerLifecycle {
|
|
|
5744
5778
|
this.assertOpen();
|
|
5745
5779
|
if (this.reconcilePromise)
|
|
5746
5780
|
return this.reconcilePromise;
|
|
5747
|
-
this.reconcilePromise = this.performReconcile().
|
|
5748
|
-
|
|
5781
|
+
this.reconcilePromise = this.performReconcile().then((completed) => {
|
|
5782
|
+
if (completed)
|
|
5783
|
+
this.lastReconcileAt = Date.now();
|
|
5784
|
+
}).finally(() => {
|
|
5749
5785
|
this.reconcilePromise = undefined;
|
|
5750
5786
|
});
|
|
5751
5787
|
return this.reconcilePromise;
|
|
@@ -5753,83 +5789,285 @@ class DockerContainerLifecycle {
|
|
|
5753
5789
|
async performReconcile() {
|
|
5754
5790
|
const listed = await listManagedContainers(this.runner);
|
|
5755
5791
|
if (!listed)
|
|
5756
|
-
return;
|
|
5792
|
+
return false;
|
|
5757
5793
|
const names = new Set(listed.map((container) => container.containerName));
|
|
5758
|
-
const toStop = new
|
|
5794
|
+
const toStop = new Map;
|
|
5795
|
+
const toRemove = new Map;
|
|
5759
5796
|
for (const container of listed) {
|
|
5760
|
-
if (this.registry.has(container.containerName))
|
|
5761
|
-
continue;
|
|
5762
5797
|
this.registry.adopt(container);
|
|
5763
|
-
if (container.
|
|
5764
|
-
|
|
5798
|
+
if (this.workspaceIsOrphaned(container.workspace) && this.registry.claimUnleased(container, this.runtimeId, CONTAINER_LEASE_MS)) {
|
|
5799
|
+
if (containerNeedsStop(container.state))
|
|
5800
|
+
toStop.set(container.containerName, container);
|
|
5801
|
+
else
|
|
5802
|
+
toRemove.set(container.containerName, container);
|
|
5803
|
+
} else if (containerNeedsStop(container.state) && this.registry.claimUnleased(container, this.runtimeId, CONTAINER_LEASE_MS)) {
|
|
5804
|
+
toStop.set(container.containerName, container);
|
|
5805
|
+
}
|
|
5765
5806
|
}
|
|
5766
5807
|
this.registry.deleteMissing(names);
|
|
5767
|
-
const abandoned = this.registry.expireLeases();
|
|
5808
|
+
const abandoned = this.registry.expireLeases(this.runtimeId, CONTAINER_LEASE_MS);
|
|
5768
5809
|
for (const identity of abandoned) {
|
|
5769
5810
|
const container = listed.find((candidate) => candidate.containerName === identity.containerName);
|
|
5770
|
-
if (container
|
|
5771
|
-
|
|
5811
|
+
if (container && managedContainerIdentityMatches(container, identity)) {
|
|
5812
|
+
if (this.workspaceIsOrphaned(container.workspace))
|
|
5813
|
+
toRemove.set(identity.containerName, identity);
|
|
5814
|
+
else if (containerNeedsStop(container.state))
|
|
5815
|
+
toStop.set(identity.containerName, identity);
|
|
5816
|
+
else
|
|
5817
|
+
toRemove.set(identity.containerName, identity);
|
|
5818
|
+
} else {
|
|
5819
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5820
|
+
}
|
|
5772
5821
|
}
|
|
5773
|
-
await Promise.allSettled([...toStop].map((
|
|
5822
|
+
await Promise.allSettled([...toStop.values()].map((identity) => this.stopIfOwned(identity)));
|
|
5774
5823
|
const claimed = this.registry.claimExpiredIdle(this.runtimeId, this.idleTtlMs);
|
|
5775
|
-
|
|
5776
|
-
|
|
5824
|
+
for (const identity of claimed)
|
|
5825
|
+
toRemove.set(identity.containerName, identity);
|
|
5826
|
+
await Promise.all([...toRemove.values()].map((identity) => this.removeIfOwned(identity)));
|
|
5827
|
+
return true;
|
|
5828
|
+
}
|
|
5829
|
+
workspaceIsOrphaned(workspace) {
|
|
5830
|
+
try {
|
|
5831
|
+
return !statSync2(workspace).isDirectory();
|
|
5832
|
+
} catch {
|
|
5833
|
+
return true;
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
async stopIfOwned(identity) {
|
|
5837
|
+
let release = true;
|
|
5838
|
+
try {
|
|
5839
|
+
const inspected = await this.inspectContainer(identity.containerName);
|
|
5840
|
+
if (inspected.error) {
|
|
5841
|
+
release = false;
|
|
5842
|
+
return;
|
|
5843
|
+
}
|
|
5844
|
+
if (!inspected.exists || !managedContainerBelongsTo(inspected.labels, identity)) {
|
|
5845
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5846
|
+
return;
|
|
5847
|
+
}
|
|
5848
|
+
await this.runner("docker", ["stop", "-t", "1", identity.containerName], {
|
|
5849
|
+
timeoutMs: 1250
|
|
5850
|
+
});
|
|
5851
|
+
} catch {} finally {
|
|
5852
|
+
if (release)
|
|
5853
|
+
this.registry.release(identity.containerName, this.runtimeId);
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
async removeIfOwned(identity) {
|
|
5857
|
+
try {
|
|
5858
|
+
const inspected = await this.inspectContainer(identity.containerName);
|
|
5859
|
+
if (inspected.error)
|
|
5860
|
+
return;
|
|
5861
|
+
if (!inspected.exists || !managedContainerBelongsTo(inspected.labels, identity)) {
|
|
5862
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
const result = await this.runner("docker", ["rm", "-f", "-v", identity.containerName], {
|
|
5866
|
+
timeoutMs: 3000
|
|
5867
|
+
});
|
|
5777
5868
|
if (result.exitCode === 0 || containerDoesNotExist(result))
|
|
5778
5869
|
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5779
5870
|
else
|
|
5780
5871
|
this.registry.release(identity.containerName, this.runtimeId);
|
|
5781
|
-
}
|
|
5872
|
+
} catch {
|
|
5873
|
+
this.registry.release(identity.containerName, this.runtimeId);
|
|
5874
|
+
}
|
|
5782
5875
|
}
|
|
5783
5876
|
async suspendAll() {
|
|
5784
5877
|
if (this.disposed)
|
|
5785
5878
|
return;
|
|
5879
|
+
if (this.suspendPromise)
|
|
5880
|
+
return this.suspendPromise;
|
|
5881
|
+
this.suspending = true;
|
|
5882
|
+
this.suspendPromise = this.suspendUnlocked();
|
|
5883
|
+
return this.suspendPromise;
|
|
5884
|
+
}
|
|
5885
|
+
async suspendUnlocked() {
|
|
5886
|
+
await Promise.all([this.waitForOperations(), ...this.reconcilePromise ? [this.reconcilePromise.catch(() => {
|
|
5887
|
+
return;
|
|
5888
|
+
})] : []]);
|
|
5786
5889
|
const identities = [...this.acquired.values()];
|
|
5787
5890
|
await Promise.allSettled(identities.map(async (identity) => {
|
|
5788
5891
|
if (!this.registry.ownedBy(identity.containerName, this.runtimeId))
|
|
5789
5892
|
return;
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5893
|
+
try {
|
|
5894
|
+
const inspected = await this.inspectContainer(identity.containerName);
|
|
5895
|
+
if (inspected.error) {
|
|
5896
|
+
this.stopFailures.add(identity.containerName);
|
|
5897
|
+
return;
|
|
5898
|
+
}
|
|
5899
|
+
if (!inspected.exists || !managedContainerBelongsTo(inspected.labels, identity)) {
|
|
5900
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5901
|
+
this.acquired.delete(identity.containerName);
|
|
5902
|
+
this.stopFailures.delete(identity.containerName);
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
const result = await this.runner("docker", ["stop", "-t", "1", identity.containerName], {
|
|
5906
|
+
timeoutMs: 1250
|
|
5907
|
+
});
|
|
5908
|
+
if (result.exitCode !== 0 && !containerDoesNotExist(result) && !containerAlreadyStopped(result)) {
|
|
5909
|
+
this.stopFailures.add(identity.containerName);
|
|
5910
|
+
return;
|
|
5911
|
+
}
|
|
5912
|
+
this.registry.release(identity.containerName, this.runtimeId);
|
|
5913
|
+
this.acquired.delete(identity.containerName);
|
|
5914
|
+
this.stopFailures.delete(identity.containerName);
|
|
5915
|
+
} catch {
|
|
5916
|
+
this.stopFailures.add(identity.containerName);
|
|
5917
|
+
}
|
|
5795
5918
|
}));
|
|
5796
5919
|
}
|
|
5797
5920
|
async remove(identity) {
|
|
5798
5921
|
this.assertOpen();
|
|
5799
|
-
this.
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5922
|
+
this.beginOperation();
|
|
5923
|
+
try {
|
|
5924
|
+
this.registry.claim(identity, this.runtimeId, CONTAINER_LEASE_MS);
|
|
5925
|
+
this.acquired.set(identity.containerName, identity);
|
|
5926
|
+
let inspected;
|
|
5927
|
+
try {
|
|
5928
|
+
inspected = await this.inspectContainer(identity.containerName);
|
|
5929
|
+
} catch (error) {
|
|
5930
|
+
this.stopFailures.add(identity.containerName);
|
|
5806
5931
|
return {
|
|
5807
|
-
|
|
5808
|
-
|
|
5932
|
+
exitCode: 1,
|
|
5933
|
+
stdout: "",
|
|
5934
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
5935
|
+
durationMs: 0,
|
|
5936
|
+
timedOut: false
|
|
5937
|
+
};
|
|
5938
|
+
}
|
|
5939
|
+
if (inspected.error) {
|
|
5940
|
+
this.stopFailures.add(identity.containerName);
|
|
5941
|
+
return inspected.error;
|
|
5942
|
+
}
|
|
5943
|
+
if (inspected.exists && !managedContainerBelongsTo(inspected.labels, identity)) {
|
|
5944
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5945
|
+
this.acquired.delete(identity.containerName);
|
|
5946
|
+
this.stopFailures.delete(identity.containerName);
|
|
5947
|
+
return {
|
|
5948
|
+
exitCode: 1,
|
|
5949
|
+
stdout: "",
|
|
5950
|
+
stderr: `refusing to remove container ${identity.containerName}: ownership labels do not match this Farai session`,
|
|
5951
|
+
durationMs: 0,
|
|
5809
5952
|
timedOut: false
|
|
5810
5953
|
};
|
|
5954
|
+
}
|
|
5955
|
+
const result = await this.runner("docker", ["rm", "-f", "-v", identity.containerName], {
|
|
5956
|
+
timeoutMs: 3000
|
|
5957
|
+
});
|
|
5958
|
+
if (result.exitCode === 0 || containerDoesNotExist(result)) {
|
|
5959
|
+
this.registry.delete(identity.containerName, this.runtimeId);
|
|
5960
|
+
this.acquired.delete(identity.containerName);
|
|
5961
|
+
this.stopFailures.delete(identity.containerName);
|
|
5962
|
+
if (result.exitCode !== 0)
|
|
5963
|
+
return {
|
|
5964
|
+
...result,
|
|
5965
|
+
exitCode: 0,
|
|
5966
|
+
timedOut: false
|
|
5967
|
+
};
|
|
5968
|
+
} else {
|
|
5969
|
+
this.stopFailures.add(identity.containerName);
|
|
5970
|
+
}
|
|
5971
|
+
return result;
|
|
5972
|
+
} finally {
|
|
5973
|
+
this.endOperation();
|
|
5811
5974
|
}
|
|
5812
|
-
return result;
|
|
5813
5975
|
}
|
|
5814
5976
|
dispose() {
|
|
5977
|
+
if (this.disposePromise)
|
|
5978
|
+
return this.disposePromise;
|
|
5815
5979
|
if (this.disposed)
|
|
5816
5980
|
return;
|
|
5817
|
-
for (const identity of this.acquired.values())
|
|
5818
|
-
this.registry.release(identity.containerName, this.runtimeId);
|
|
5819
|
-
this.acquired.clear();
|
|
5820
|
-
this.registry.close();
|
|
5821
5981
|
this.disposed = true;
|
|
5982
|
+
clearInterval(this.reconcileTimer);
|
|
5983
|
+
const finalize = () => {
|
|
5984
|
+
try {
|
|
5985
|
+
for (const identity of this.acquired.values()) {
|
|
5986
|
+
if (!this.stopFailures.has(identity.containerName))
|
|
5987
|
+
this.registry.release(identity.containerName, this.runtimeId);
|
|
5988
|
+
}
|
|
5989
|
+
} finally {
|
|
5990
|
+
this.acquired.clear();
|
|
5991
|
+
this.stopFailures.clear();
|
|
5992
|
+
this.registry.close();
|
|
5993
|
+
}
|
|
5994
|
+
};
|
|
5995
|
+
const waits = [...this.reconcilePromise ? [this.reconcilePromise.catch(() => {
|
|
5996
|
+
return;
|
|
5997
|
+
})] : [], ...this.suspendPromise ? [this.suspendPromise.catch(() => {
|
|
5998
|
+
return;
|
|
5999
|
+
})] : [], this.waitForOperations()];
|
|
6000
|
+
if (!this.reconcilePromise && !this.suspendPromise && this.activeOperations === 0) {
|
|
6001
|
+
finalize();
|
|
6002
|
+
return;
|
|
6003
|
+
}
|
|
6004
|
+
this.disposePromise = Promise.all(waits).then(finalize);
|
|
6005
|
+
return this.disposePromise;
|
|
6006
|
+
}
|
|
6007
|
+
beginOperation() {
|
|
6008
|
+
if (this.suspending)
|
|
6009
|
+
throw new Error("container lifecycle is closing");
|
|
6010
|
+
this.activeOperations += 1;
|
|
6011
|
+
}
|
|
6012
|
+
endOperation() {
|
|
6013
|
+
this.activeOperations -= 1;
|
|
6014
|
+
if (this.activeOperations === 0) {
|
|
6015
|
+
this.resolveOperationDrain?.();
|
|
6016
|
+
this.resolveOperationDrain = undefined;
|
|
6017
|
+
this.operationDrain = undefined;
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
waitForOperations() {
|
|
6021
|
+
if (this.activeOperations === 0)
|
|
6022
|
+
return Promise.resolve();
|
|
6023
|
+
if (!this.operationDrain) {
|
|
6024
|
+
this.operationDrain = new Promise((resolve4) => {
|
|
6025
|
+
this.resolveOperationDrain = resolve4;
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
return this.operationDrain;
|
|
6029
|
+
}
|
|
6030
|
+
async inspectContainer(containerName) {
|
|
6031
|
+
const result = await this.runner("docker", ["inspect", "-f", "{{json .Config.Labels}}", containerName]);
|
|
6032
|
+
if (result.exitCode !== 0) {
|
|
6033
|
+
if (containerDoesNotExist(result))
|
|
6034
|
+
return {
|
|
6035
|
+
exists: false
|
|
6036
|
+
};
|
|
6037
|
+
return {
|
|
6038
|
+
exists: true,
|
|
6039
|
+
error: result
|
|
6040
|
+
};
|
|
6041
|
+
}
|
|
6042
|
+
try {
|
|
6043
|
+
const labels = JSON.parse(result.stdout.trim());
|
|
6044
|
+
return {
|
|
6045
|
+
exists: true,
|
|
6046
|
+
...labels ? {
|
|
6047
|
+
labels
|
|
6048
|
+
} : {}
|
|
6049
|
+
};
|
|
6050
|
+
} catch {
|
|
6051
|
+
return {
|
|
6052
|
+
exists: true
|
|
6053
|
+
};
|
|
6054
|
+
}
|
|
5822
6055
|
}
|
|
5823
6056
|
assertOpen() {
|
|
5824
6057
|
if (this.disposed)
|
|
5825
6058
|
throw new Error("container lifecycle is closed");
|
|
6059
|
+
if (this.suspending)
|
|
6060
|
+
throw new Error("container lifecycle is closing");
|
|
5826
6061
|
}
|
|
5827
6062
|
}
|
|
5828
6063
|
function managedContainerLabels(identity) {
|
|
5829
6064
|
return ["--label", `${FARAI_MANAGED_LABEL}=true`, "--label", `${FARAI_CONTAINER_KIND_LABEL}=${FARAI_INTERACTIVE_CONTAINER_KIND}`, "--label", `${FARAI_ROOT_SESSION_LABEL}=${identity.rootSessionId}`, "--label", `${FARAI_WORKSPACE_LABEL}=${encodeWorkspace(identity.workspace)}`, "--label", `${FARAI_WORKSPACE_HASH_LABEL}=${workspaceHash(identity.workspace)}`, "--label", `${FARAI_IMAGE_CONTRACT_LABEL}=${identity.imageContract}`];
|
|
5830
6065
|
}
|
|
5831
6066
|
function managedContainerLabelsMatch(labels, identity) {
|
|
5832
|
-
return
|
|
6067
|
+
return managedContainerBelongsTo(labels, identity) && labels?.[FARAI_IMAGE_CONTRACT_LABEL] === identity.imageContract;
|
|
6068
|
+
}
|
|
6069
|
+
function managedContainerBelongsTo(labels, identity) {
|
|
6070
|
+
return labels?.[FARAI_MANAGED_LABEL] === "true" && labels[FARAI_CONTAINER_KIND_LABEL] === FARAI_INTERACTIVE_CONTAINER_KIND && labels[FARAI_ROOT_SESSION_LABEL] === identity.rootSessionId && labels[FARAI_WORKSPACE_LABEL] === encodeWorkspace(identity.workspace) && (!labels[FARAI_WORKSPACE_HASH_LABEL] || labels[FARAI_WORKSPACE_HASH_LABEL] === workspaceHash(identity.workspace));
|
|
5833
6071
|
}
|
|
5834
6072
|
function workspaceHash(workspace) {
|
|
5835
6073
|
return createHash3("sha256").update(resolve3(workspace)).digest("hex");
|
|
@@ -5906,22 +6144,34 @@ class ContainerLeaseRegistry {
|
|
|
5906
6144
|
$now: now
|
|
5907
6145
|
});
|
|
5908
6146
|
}
|
|
5909
|
-
expireLeases() {
|
|
6147
|
+
expireLeases(runtimeId, leaseMs) {
|
|
5910
6148
|
const now = new Date().toISOString();
|
|
5911
6149
|
const rows = this.db.query(`select * from container_leases
|
|
5912
|
-
where lease_owner is not null and lease_expires_at is not null and lease_expires_at <= $now`).all({
|
|
5913
|
-
$now: now
|
|
6150
|
+
where lease_owner is not null and lease_owner != $owner and lease_expires_at is not null and lease_expires_at <= $now`).all({
|
|
6151
|
+
$now: now,
|
|
6152
|
+
$owner: runtimeId
|
|
5914
6153
|
});
|
|
5915
|
-
const
|
|
5916
|
-
|
|
6154
|
+
const expires = new Date(Date.now() + leaseMs).toISOString();
|
|
6155
|
+
const update = this.db.query(`update container_leases set lease_owner = $owner, lease_expires_at = $expires,
|
|
6156
|
+
idle_since = coalesce(idle_since, $idle), last_used_at = $now
|
|
6157
|
+
where container_name = $name and lease_owner = $previousOwner and lease_expires_at = $previousExpires`);
|
|
6158
|
+
const claimed = [];
|
|
5917
6159
|
this.db.transaction(() => {
|
|
5918
|
-
for (const row of rows)
|
|
5919
|
-
update.run({
|
|
6160
|
+
for (const row of rows) {
|
|
6161
|
+
const result = update.run({
|
|
5920
6162
|
$name: row.container_name,
|
|
5921
|
-
$
|
|
6163
|
+
$previousOwner: row.lease_owner,
|
|
6164
|
+
$previousExpires: row.lease_expires_at,
|
|
6165
|
+
$owner: runtimeId,
|
|
6166
|
+
$expires: expires,
|
|
6167
|
+
$idle: row.lease_expires_at ?? now,
|
|
6168
|
+
$now: now
|
|
5922
6169
|
});
|
|
5923
|
-
|
|
5924
|
-
|
|
6170
|
+
if (result.changes === 1)
|
|
6171
|
+
claimed.push(row);
|
|
6172
|
+
}
|
|
6173
|
+
}).immediate();
|
|
6174
|
+
return claimed.map(identityFromRow);
|
|
5925
6175
|
}
|
|
5926
6176
|
claimExpiredIdle(runtimeId, idleTtlMs) {
|
|
5927
6177
|
const cutoff = new Date(Date.now() - Math.max(0, idleTtlMs)).toISOString();
|
|
@@ -5931,14 +6181,16 @@ class ContainerLeaseRegistry {
|
|
|
5931
6181
|
});
|
|
5932
6182
|
const expires = new Date(Date.now() + CONTAINER_LEASE_MS).toISOString();
|
|
5933
6183
|
const claim = this.db.query(`update container_leases set lease_owner = $owner, lease_expires_at = $expires
|
|
5934
|
-
where container_name = $name and lease_owner is null
|
|
6184
|
+
where container_name = $name and lease_owner is null and lease_expires_at is null
|
|
6185
|
+
and idle_since = $idle`);
|
|
5935
6186
|
const claimed = [];
|
|
5936
6187
|
this.db.transaction(() => {
|
|
5937
6188
|
for (const row of rows) {
|
|
5938
6189
|
const result = claim.run({
|
|
5939
6190
|
$name: row.container_name,
|
|
5940
6191
|
$owner: runtimeId,
|
|
5941
|
-
$expires: expires
|
|
6192
|
+
$expires: expires,
|
|
6193
|
+
$idle: row.idle_since
|
|
5942
6194
|
});
|
|
5943
6195
|
if (result.changes === 1)
|
|
5944
6196
|
claimed.push(row);
|
|
@@ -5948,9 +6200,22 @@ class ContainerLeaseRegistry {
|
|
|
5948
6200
|
}
|
|
5949
6201
|
adopt(identity) {
|
|
5950
6202
|
const now = new Date().toISOString();
|
|
5951
|
-
this.db.query(`insert
|
|
6203
|
+
this.db.query(`insert into container_leases
|
|
5952
6204
|
(container_name, root_session_id, workspace, image_contract, lease_owner, lease_expires_at, idle_since, last_used_at)
|
|
5953
|
-
values ($name, $root, $workspace, $contract, null, null, $now, $now)
|
|
6205
|
+
values ($name, $root, $workspace, $contract, null, null, $now, $now)
|
|
6206
|
+
on conflict(container_name) do update set
|
|
6207
|
+
root_session_id = excluded.root_session_id,
|
|
6208
|
+
workspace = excluded.workspace,
|
|
6209
|
+
image_contract = excluded.image_contract,
|
|
6210
|
+
idle_since = case when container_leases.root_session_id <> excluded.root_session_id
|
|
6211
|
+
or container_leases.workspace <> excluded.workspace
|
|
6212
|
+
or container_leases.image_contract <> excluded.image_contract
|
|
6213
|
+
then excluded.idle_since else coalesce(container_leases.idle_since, excluded.idle_since) end,
|
|
6214
|
+
last_used_at = case when container_leases.root_session_id <> excluded.root_session_id
|
|
6215
|
+
or container_leases.workspace <> excluded.workspace
|
|
6216
|
+
or container_leases.image_contract <> excluded.image_contract
|
|
6217
|
+
then excluded.last_used_at else container_leases.last_used_at end
|
|
6218
|
+
where container_leases.lease_owner is null`).run({
|
|
5954
6219
|
$name: identity.containerName,
|
|
5955
6220
|
$root: identity.rootSessionId,
|
|
5956
6221
|
$workspace: resolve3(identity.workspace),
|
|
@@ -5958,6 +6223,20 @@ class ContainerLeaseRegistry {
|
|
|
5958
6223
|
$now: now
|
|
5959
6224
|
});
|
|
5960
6225
|
}
|
|
6226
|
+
claimUnleased(identity, runtimeId, leaseMs) {
|
|
6227
|
+
const expires = new Date(Date.now() + leaseMs).toISOString();
|
|
6228
|
+
const result = this.db.query(`update container_leases set lease_owner = $owner, lease_expires_at = $expires
|
|
6229
|
+
where container_name = $name and root_session_id = $root and workspace = $workspace
|
|
6230
|
+
and image_contract = $contract and lease_owner is null and lease_expires_at is null`).run({
|
|
6231
|
+
$name: identity.containerName,
|
|
6232
|
+
$root: identity.rootSessionId,
|
|
6233
|
+
$workspace: resolve3(identity.workspace),
|
|
6234
|
+
$contract: identity.imageContract,
|
|
6235
|
+
$owner: runtimeId,
|
|
6236
|
+
$expires: expires
|
|
6237
|
+
});
|
|
6238
|
+
return result.changes === 1;
|
|
6239
|
+
}
|
|
5961
6240
|
has(containerName) {
|
|
5962
6241
|
return Boolean(this.row(containerName));
|
|
5963
6242
|
}
|
|
@@ -6012,15 +6291,22 @@ class ContainerLeaseRegistry {
|
|
|
6012
6291
|
}
|
|
6013
6292
|
}
|
|
6014
6293
|
async function listManagedContainers(runner) {
|
|
6015
|
-
const format = `{{.Names}} {{.State}} {{.Label "${FARAI_ROOT_SESSION_LABEL}"}} {{.Label "${FARAI_WORKSPACE_LABEL}"}} {{.Label "${FARAI_IMAGE_CONTRACT_LABEL}"}}`;
|
|
6016
|
-
const result = await runner("docker", ["ps", "-a", "--filter", `label=${FARAI_MANAGED_LABEL}=true`, "--filter", `label=${FARAI_CONTAINER_KIND_LABEL}=${FARAI_INTERACTIVE_CONTAINER_KIND}`, "--format", format]
|
|
6294
|
+
const format = `{{.Names}} {{.State}} {{.Label "${FARAI_ROOT_SESSION_LABEL}"}} {{.Label "${FARAI_WORKSPACE_LABEL}"}} {{.Label "${FARAI_WORKSPACE_HASH_LABEL}"}} {{.Label "${FARAI_IMAGE_CONTRACT_LABEL}"}}`;
|
|
6295
|
+
const result = await runner("docker", ["ps", "-a", "--filter", `label=${FARAI_MANAGED_LABEL}=true`, "--filter", `label=${FARAI_CONTAINER_KIND_LABEL}=${FARAI_INTERACTIVE_CONTAINER_KIND}`, "--format", format], {
|
|
6296
|
+
timeoutMs: 3000
|
|
6297
|
+
});
|
|
6017
6298
|
if (result.exitCode !== 0)
|
|
6018
6299
|
return;
|
|
6019
6300
|
return result.stdout.split(`
|
|
6020
6301
|
`).flatMap((line) => {
|
|
6021
|
-
const
|
|
6302
|
+
const fields = line.trim().split("\t");
|
|
6303
|
+
const [containerName, state, rootSessionId, encodedWorkspace] = fields;
|
|
6304
|
+
const encodedWorkspaceHash = fields.length >= 6 ? fields[4] : "";
|
|
6305
|
+
const imageContract = fields.length >= 6 ? fields[5] : fields[4];
|
|
6022
6306
|
const workspace = encodedWorkspace ? decodeWorkspace(encodedWorkspace) : undefined;
|
|
6023
|
-
if (!containerName || !state || !rootSessionId || !workspace || !imageContract)
|
|
6307
|
+
if (!containerName || !containerName.startsWith(FARAI_CONTAINER_NAME_PREFIX) || !state || !rootSessionId || !workspace || !imageContract)
|
|
6308
|
+
return [];
|
|
6309
|
+
if (encodedWorkspaceHash && encodedWorkspaceHash !== workspaceHash(workspace))
|
|
6024
6310
|
return [];
|
|
6025
6311
|
return [{
|
|
6026
6312
|
containerName,
|
|
@@ -6040,20 +6326,29 @@ function identityFromRow(row) {
|
|
|
6040
6326
|
};
|
|
6041
6327
|
}
|
|
6042
6328
|
function containerDoesNotExist(result) {
|
|
6043
|
-
return /no such container/i.test(`${result.stdout}
|
|
6329
|
+
return /no such (container|object)/i.test(`${result.stdout}
|
|
6044
6330
|
${result.stderr}`);
|
|
6045
6331
|
}
|
|
6046
6332
|
function containerAlreadyStopped(result) {
|
|
6047
6333
|
return /is not running|already stopped/i.test(`${result.stdout}
|
|
6048
6334
|
${result.stderr}`);
|
|
6049
6335
|
}
|
|
6050
|
-
|
|
6336
|
+
function containerNeedsStop(state) {
|
|
6337
|
+
return ["created", "running", "restarting", "paused"].includes(state.toLowerCase());
|
|
6338
|
+
}
|
|
6339
|
+
function managedContainerIdentityMatches(left, right) {
|
|
6340
|
+
return left.rootSessionId === right.rootSessionId && resolve3(left.workspace) === resolve3(right.workspace);
|
|
6341
|
+
}
|
|
6342
|
+
async function runDockerProcess(command, args, options = {}) {
|
|
6051
6343
|
return await runCapturedProcess(command, args, {
|
|
6052
|
-
timeoutMs,
|
|
6344
|
+
timeoutMs: options.timeoutMs ?? 3000,
|
|
6345
|
+
...options.signal ? {
|
|
6346
|
+
signal: options.signal
|
|
6347
|
+
} : {},
|
|
6053
6348
|
env: faraiDockerEnvironment()
|
|
6054
6349
|
});
|
|
6055
6350
|
}
|
|
6056
|
-
var FARAI_MANAGED_LABEL = "org.farai.managed", FARAI_CONTAINER_KIND_LABEL = "org.farai.kind", FARAI_ROOT_SESSION_LABEL = "org.farai.root-session", FARAI_WORKSPACE_LABEL = "org.farai.workspace", FARAI_WORKSPACE_HASH_LABEL = "org.farai.workspace-hash", FARAI_IMAGE_CONTRACT_LABEL = "org.farai.image-contract", FARAI_INTERACTIVE_CONTAINER_KIND = "interactive", CONTAINER_LEASE_MS = 60000, CONTAINER_IDLE_TTL_MS;
|
|
6351
|
+
var FARAI_MANAGED_LABEL = "org.farai.managed", FARAI_CONTAINER_KIND_LABEL = "org.farai.kind", FARAI_ROOT_SESSION_LABEL = "org.farai.root-session", FARAI_WORKSPACE_LABEL = "org.farai.workspace", FARAI_WORKSPACE_HASH_LABEL = "org.farai.workspace-hash", FARAI_IMAGE_CONTRACT_LABEL = "org.farai.image-contract", FARAI_INTERACTIVE_CONTAINER_KIND = "interactive", FARAI_CONTAINER_NAME_PREFIX = "farai-kali-", CONTAINER_LEASE_MS = 60000, CONTAINER_IDLE_TTL_MS, CONTAINER_RECONCILE_INTERVAL_MS = 60000;
|
|
6057
6352
|
var init_lifecycle = __esm(() => {
|
|
6058
6353
|
init_config();
|
|
6059
6354
|
init_captured_process();
|
|
@@ -6121,12 +6416,15 @@ class KaliContainerBackend {
|
|
|
6121
6416
|
async status() {
|
|
6122
6417
|
const image = await this.resolveImage();
|
|
6123
6418
|
const dockerContext = (await this.processRunner("docker", ["context", "show"])).stdout.trim();
|
|
6124
|
-
const
|
|
6419
|
+
const inspectedResult = await this.inspectPersistent();
|
|
6420
|
+
const inspected = inspectedResult.container;
|
|
6125
6421
|
const persistentExists = Boolean(inspected);
|
|
6126
6422
|
const persistentRunning = inspected?.state === "running";
|
|
6127
6423
|
const persistentImageId = inspected?.imageId ?? "";
|
|
6128
6424
|
const persistentImageCurrent = Boolean(persistentExists && image.id && persistentImageId && imageIdsMatch(image.id, persistentImageId));
|
|
6129
6425
|
const persistentIdentityCurrent = Boolean(this.identity && managedContainerLabelsMatch(inspected?.labels, this.identity));
|
|
6426
|
+
const persistentManaged = Boolean(this.identity && managedContainerBelongsTo(inspected?.labels, this.identity));
|
|
6427
|
+
const dockerError = image.error ?? inspectedResult.error;
|
|
6130
6428
|
return {
|
|
6131
6429
|
image: this.image,
|
|
6132
6430
|
imageExists: image.exists,
|
|
@@ -6150,30 +6448,43 @@ class KaliContainerBackend {
|
|
|
6150
6448
|
persistentImageId
|
|
6151
6449
|
} : {},
|
|
6152
6450
|
persistentImageCurrent,
|
|
6153
|
-
persistentIdentityCurrent
|
|
6451
|
+
persistentIdentityCurrent,
|
|
6452
|
+
persistentManaged,
|
|
6453
|
+
...dockerError ? {
|
|
6454
|
+
dockerError
|
|
6455
|
+
} : {}
|
|
6154
6456
|
};
|
|
6155
6457
|
}
|
|
6156
6458
|
async inspectPersistent() {
|
|
6157
6459
|
const inspected = await this.processRunner("docker", ["inspect", "-f", "{{.State.Status}}\t{{.Image}}\t{{json .Config.Labels}}", this.containerName]);
|
|
6158
|
-
if (inspected.exitCode !== 0)
|
|
6159
|
-
|
|
6460
|
+
if (inspected.exitCode !== 0) {
|
|
6461
|
+
if (containerDoesNotExist2(inspected) || !inspected.stdout.trim() && !inspected.stderr.trim())
|
|
6462
|
+
return {};
|
|
6463
|
+
return {
|
|
6464
|
+
error: dockerFailure(inspected, `unable to inspect persistent Kali container ${this.containerName}`)
|
|
6465
|
+
};
|
|
6466
|
+
}
|
|
6160
6467
|
const [state = "unknown", imageId = "", labelsJson = ""] = inspected.stdout.trim().split("\t", 3);
|
|
6161
6468
|
try {
|
|
6162
6469
|
return {
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
imageId
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6470
|
+
container: {
|
|
6471
|
+
state: state || "unknown",
|
|
6472
|
+
...imageId ? {
|
|
6473
|
+
imageId
|
|
6474
|
+
} : {},
|
|
6475
|
+
...labelsJson && labelsJson !== "null" ? {
|
|
6476
|
+
labels: JSON.parse(labelsJson)
|
|
6477
|
+
} : {}
|
|
6478
|
+
}
|
|
6170
6479
|
};
|
|
6171
6480
|
} catch {
|
|
6172
6481
|
return {
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
imageId
|
|
6176
|
-
|
|
6482
|
+
container: {
|
|
6483
|
+
state: state || "unknown",
|
|
6484
|
+
...imageId ? {
|
|
6485
|
+
imageId
|
|
6486
|
+
} : {}
|
|
6487
|
+
}
|
|
6177
6488
|
};
|
|
6178
6489
|
}
|
|
6179
6490
|
}
|
|
@@ -6191,6 +6502,12 @@ class KaliContainerBackend {
|
|
|
6191
6502
|
exists: false
|
|
6192
6503
|
};
|
|
6193
6504
|
const listed = await this.processRunner("docker", ["image", "ls", repository, "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"]);
|
|
6505
|
+
if (listed.exitCode !== 0) {
|
|
6506
|
+
return {
|
|
6507
|
+
exists: false,
|
|
6508
|
+
error: dockerFailure(listed, `unable to inspect local Docker images for ${this.image}`)
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6194
6511
|
const line = listed.stdout.split(`
|
|
6195
6512
|
`).map((candidate) => candidate.trim()).find((candidate) => candidate.startsWith(`${repository}:${tag} `));
|
|
6196
6513
|
if (!line)
|
|
@@ -6206,7 +6523,8 @@ class KaliContainerBackend {
|
|
|
6206
6523
|
if (inspectById.exitCode !== 0)
|
|
6207
6524
|
return {
|
|
6208
6525
|
exists: true,
|
|
6209
|
-
id: id2
|
|
6526
|
+
id: id2,
|
|
6527
|
+
error: dockerFailure(inspectById, `unable to inspect local Docker image ${id2}`)
|
|
6210
6528
|
};
|
|
6211
6529
|
const resolved = parseImageInspect(inspectById.stdout);
|
|
6212
6530
|
return {
|
|
@@ -6229,8 +6547,14 @@ class KaliContainerBackend {
|
|
|
6229
6547
|
}
|
|
6230
6548
|
}
|
|
6231
6549
|
async startPersistentUnlocked() {
|
|
6550
|
+
if (this.identity && this.lifecycle?.withLease) {
|
|
6551
|
+
return await this.lifecycle.withLease(this.identity, () => this.startPersistentBody());
|
|
6552
|
+
}
|
|
6232
6553
|
if (this.identity && this.lifecycle)
|
|
6233
6554
|
await this.lifecycle.acquire(this.identity);
|
|
6555
|
+
return await this.startPersistentBody();
|
|
6556
|
+
}
|
|
6557
|
+
async startPersistentBody() {
|
|
6234
6558
|
try {
|
|
6235
6559
|
const status = await this.status();
|
|
6236
6560
|
if (!status.imageExists) {
|
|
@@ -6239,7 +6563,28 @@ class KaliContainerBackend {
|
|
|
6239
6563
|
return {
|
|
6240
6564
|
exitCode: 1,
|
|
6241
6565
|
stdout: "",
|
|
6242
|
-
stderr: `kali image ${this.image} is missing; run \`farai setup --no-kb\``,
|
|
6566
|
+
stderr: status.dockerError ?? `kali image ${this.image} is missing; run \`farai setup --no-kb\``,
|
|
6567
|
+
durationMs: 0,
|
|
6568
|
+
timedOut: false
|
|
6569
|
+
};
|
|
6570
|
+
}
|
|
6571
|
+
if (status.dockerError) {
|
|
6572
|
+
if (this.identity && this.lifecycle)
|
|
6573
|
+
this.lifecycle.release(this.identity);
|
|
6574
|
+
return {
|
|
6575
|
+
exitCode: 1,
|
|
6576
|
+
stdout: "",
|
|
6577
|
+
stderr: status.dockerError,
|
|
6578
|
+
durationMs: 0,
|
|
6579
|
+
timedOut: false
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
if (status.persistentExists && this.identity && !status.persistentManaged) {
|
|
6583
|
+
this.lifecycle?.release(this.identity);
|
|
6584
|
+
return {
|
|
6585
|
+
exitCode: 1,
|
|
6586
|
+
stdout: "",
|
|
6587
|
+
stderr: `refusing to use container ${this.containerName}: ownership labels do not match this Farai session`,
|
|
6243
6588
|
durationMs: 0,
|
|
6244
6589
|
timedOut: false
|
|
6245
6590
|
};
|
|
@@ -6276,7 +6621,15 @@ class KaliContainerBackend {
|
|
|
6276
6621
|
};
|
|
6277
6622
|
}
|
|
6278
6623
|
return await withGlobalContainerStartLock(async () => {
|
|
6279
|
-
await this.processRunner("docker", ["rm", "-f", "-v", this.containerName]);
|
|
6624
|
+
const removed = await this.processRunner("docker", ["rm", "-f", "-v", this.containerName]);
|
|
6625
|
+
if (removed.exitCode !== 0 && !containerDoesNotExist2(removed)) {
|
|
6626
|
+
if (this.identity && this.lifecycle)
|
|
6627
|
+
this.lifecycle.release(this.identity);
|
|
6628
|
+
return {
|
|
6629
|
+
...removed,
|
|
6630
|
+
stderr: removed.stderr || `could not remove stale Kali container ${this.containerName}`
|
|
6631
|
+
};
|
|
6632
|
+
}
|
|
6280
6633
|
const worktrees = join9(this.rootWorkspace, ".farai", "worktrees");
|
|
6281
6634
|
mkdirSync4(worktrees, {
|
|
6282
6635
|
recursive: true
|
|
@@ -6318,6 +6671,11 @@ class KaliContainerBackend {
|
|
|
6318
6671
|
};
|
|
6319
6672
|
}
|
|
6320
6673
|
async stopPersistent() {
|
|
6674
|
+
const previous = containerStartLocks.get(this.containerName);
|
|
6675
|
+
if (previous)
|
|
6676
|
+
await previous.catch(() => {
|
|
6677
|
+
return;
|
|
6678
|
+
});
|
|
6321
6679
|
if (this.identity && this.lifecycle)
|
|
6322
6680
|
return await this.lifecycle.remove(this.identity);
|
|
6323
6681
|
const started = Date.now();
|
|
@@ -6676,6 +7034,11 @@ function parseImageInspect(raw) {
|
|
|
6676
7034
|
};
|
|
6677
7035
|
}
|
|
6678
7036
|
}
|
|
7037
|
+
function dockerFailure(result, fallback) {
|
|
7038
|
+
const detail = `${result.stderr}
|
|
7039
|
+
${result.stdout}`.trim().replace(/\s+/g, " ");
|
|
7040
|
+
return detail ? `${fallback}: ${detail.slice(0, 500)}` : fallback;
|
|
7041
|
+
}
|
|
6679
7042
|
function imageIdsMatch(left, right) {
|
|
6680
7043
|
const normalizedLeft = left.replace(/^sha256:/, "");
|
|
6681
7044
|
const normalizedRight = right.replace(/^sha256:/, "");
|
|
@@ -6691,10 +7054,10 @@ async function runProcess(command, args, timeoutMs = 15000) {
|
|
|
6691
7054
|
});
|
|
6692
7055
|
}
|
|
6693
7056
|
function containerDoesNotExist2(result) {
|
|
6694
|
-
return /no such container/i.test(`${result.stdout}
|
|
7057
|
+
return /no such (container|object)/i.test(`${result.stdout}
|
|
6695
7058
|
${result.stderr}`);
|
|
6696
7059
|
}
|
|
6697
|
-
var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX
|
|
7060
|
+
var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX, KALI_IMAGE_CONTRACT, DEFAULT_KALI_IMAGE = "farai-kali:latest", KALI_IMAGE_CONTRACT_LABEL = "org.farai.kali.contract";
|
|
6698
7061
|
var init_kali = __esm(() => {
|
|
6699
7062
|
init_spawn_session();
|
|
6700
7063
|
init_pty_session();
|
|
@@ -6711,6 +7074,7 @@ var init_kali = __esm(() => {
|
|
|
6711
7074
|
`);
|
|
6712
7075
|
CONTAINER_EXEC_KILLER = ["import os, signal, sys, time", "path = sys.argv[1]", "root = None", "marker_deadline = time.monotonic() + 0.25", "while root is None and time.monotonic() < marker_deadline:", " try:", " with open(path, encoding='ascii') as handle:", " root = int(handle.read().strip())", " except (FileNotFoundError, OSError, ValueError):", " time.sleep(0.01)", "if root is None:", " raise SystemExit(0)", "def direct_children(pid):", " try:", " with open(f'/proc/{pid}/task/{pid}/children', encoding='ascii') as handle:", " return [int(value) for value in handle.read().split()]", " except (FileNotFoundError, OSError, ValueError):", " return []", "def descendants(pid):", " found = []", " pending = direct_children(pid)", " seen = set()", " while pending:", " child = pending.pop()", " if child in seen:", " continue", " seen.add(child)", " found.append(child)", " pending.extend(direct_children(child))", " return found", "def alive(pid):", " try:", " os.kill(pid, 0)", " return True", " except ProcessLookupError:", " return False", " except PermissionError:", " return True", "targets = descendants(root)", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGTERM)", " except (ProcessLookupError, PermissionError):", " pass", "deadline = time.monotonic() + 1.0", "while time.monotonic() < deadline and any(alive(pid) for pid in [root, *targets]):", " time.sleep(0.025)", "targets = list(dict.fromkeys([*targets, *descendants(root)]))", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGKILL)", " except (ProcessLookupError, PermissionError):", " pass", "try:", " os.unlink(path)", "except FileNotFoundError:", " pass"].join(`
|
|
6713
7076
|
`);
|
|
7077
|
+
CONTAINER_PREFIX = FARAI_CONTAINER_NAME_PREFIX;
|
|
6714
7078
|
KALI_IMAGE_CONTRACT = KALI_TOOL_MANIFEST.contract;
|
|
6715
7079
|
});
|
|
6716
7080
|
|
|
@@ -7463,8 +7827,8 @@ var init_process_output = __esm(() => {
|
|
|
7463
7827
|
|
|
7464
7828
|
// src/version.ts
|
|
7465
7829
|
function resolveFaraiVersion() {
|
|
7466
|
-
if ("0.2.
|
|
7467
|
-
return "0.2.
|
|
7830
|
+
if ("0.2.9")
|
|
7831
|
+
return "0.2.9";
|
|
7468
7832
|
try {
|
|
7469
7833
|
const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
|
|
7470
7834
|
if (typeof parsed.version === "string" && parsed.version)
|
|
@@ -16905,7 +17269,7 @@ var init_patch_apply = __esm(() => {
|
|
|
16905
17269
|
});
|
|
16906
17270
|
|
|
16907
17271
|
// src/agent-tools/filesystem/notebook-edit.ts
|
|
16908
|
-
import { statSync as
|
|
17272
|
+
import { statSync as statSync3 } from "fs";
|
|
16909
17273
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
16910
17274
|
function parseNotebook(text2) {
|
|
16911
17275
|
const value = JSON.parse(text2);
|
|
@@ -16922,7 +17286,7 @@ function parseNotebook(text2) {
|
|
|
16922
17286
|
return value;
|
|
16923
17287
|
}
|
|
16924
17288
|
function writeNotebookAtomically(path, content) {
|
|
16925
|
-
const mode =
|
|
17289
|
+
const mode = statSync3(path).mode & 511;
|
|
16926
17290
|
atomicWriteFile(path, content, mode);
|
|
16927
17291
|
}
|
|
16928
17292
|
function newCell(cellType, source, includeId) {
|
|
@@ -17408,6 +17772,9 @@ function parseActiveContent(value) {
|
|
|
17408
17772
|
return;
|
|
17409
17773
|
if (!validDate(record2.generatedAt) || !validDate(record2.activatedAt))
|
|
17410
17774
|
return;
|
|
17775
|
+
if (record2.sourceCommit !== undefined && (typeof record2.sourceCommit !== "string" || !SOURCE_COMMIT_PATTERN.test(record2.sourceCommit)))
|
|
17776
|
+
return;
|
|
17777
|
+
const sourceCommit = typeof record2.sourceCommit === "string" ? record2.sourceCommit : undefined;
|
|
17411
17778
|
if (typeof record2.manifestUrl !== "string" || !record2.manifestUrl)
|
|
17412
17779
|
return;
|
|
17413
17780
|
if (typeof record2.knowledge !== "boolean" || typeof record2.skills !== "boolean")
|
|
@@ -17417,6 +17784,9 @@ function parseActiveContent(value) {
|
|
|
17417
17784
|
schemaVersion: 1,
|
|
17418
17785
|
version: record2.version,
|
|
17419
17786
|
generatedAt: record2.generatedAt,
|
|
17787
|
+
...sourceCommit ? {
|
|
17788
|
+
sourceCommit
|
|
17789
|
+
} : {},
|
|
17420
17790
|
activatedAt: record2.activatedAt,
|
|
17421
17791
|
manifestUrl: record2.manifestUrl,
|
|
17422
17792
|
...previousVersion ? {
|
|
@@ -17429,18 +17799,19 @@ function parseActiveContent(value) {
|
|
|
17429
17799
|
function validDate(value) {
|
|
17430
17800
|
return typeof value === "string" && value.length > 0 && Number.isFinite(Date.parse(value));
|
|
17431
17801
|
}
|
|
17432
|
-
var POINTER_MAX_BYTES, VERSION_PATTERN;
|
|
17802
|
+
var POINTER_MAX_BYTES, VERSION_PATTERN, SOURCE_COMMIT_PATTERN;
|
|
17433
17803
|
var init_paths2 = __esm(() => {
|
|
17434
17804
|
init_paths();
|
|
17435
17805
|
init_private_path();
|
|
17436
17806
|
init_file_read();
|
|
17437
17807
|
POINTER_MAX_BYTES = 64 * 1024;
|
|
17438
17808
|
VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17809
|
+
SOURCE_COMMIT_PATTERN = /^[a-f0-9]{40}$/;
|
|
17439
17810
|
});
|
|
17440
17811
|
|
|
17441
17812
|
// src/agent-skills/registry.ts
|
|
17442
17813
|
import { createHash as createHash5 } from "crypto";
|
|
17443
|
-
import { existsSync as existsSync9, readdirSync as readdirSync3, realpathSync as realpathSync2, statSync as
|
|
17814
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
17444
17815
|
import { homedir as homedir4 } from "os";
|
|
17445
17816
|
import { delimiter, isAbsolute as isAbsolute5, join as join14, relative as relative4, resolve as resolve8, sep as sep2 } from "path";
|
|
17446
17817
|
function discoverSkills(options = {}) {
|
|
@@ -17501,7 +17872,7 @@ function loadSkill(name, options = {}) {
|
|
|
17501
17872
|
const canonical = realpathSync2(absolute);
|
|
17502
17873
|
if (!inside(skill.directory, canonical))
|
|
17503
17874
|
return;
|
|
17504
|
-
const stats =
|
|
17875
|
+
const stats = statSync4(canonical);
|
|
17505
17876
|
if (!stats.isFile() || stats.size > MAX_RESOURCE_BYTES)
|
|
17506
17877
|
return;
|
|
17507
17878
|
const content = readBoundedFileTextSync(canonical, MAX_RESOURCE_BYTES, "skill resource");
|
|
@@ -17533,11 +17904,7 @@ function renderSkillCatalog(workspace, maxChars = 8000) {
|
|
|
17533
17904
|
`), maxChars);
|
|
17534
17905
|
}
|
|
17535
17906
|
function skillRoots(options) {
|
|
17536
|
-
const roots = [
|
|
17537
|
-
path: BUILTIN_DIR,
|
|
17538
|
-
source: "builtin",
|
|
17539
|
-
priority: 0
|
|
17540
|
-
}];
|
|
17907
|
+
const roots = [];
|
|
17541
17908
|
const content = activeContentSkillsDir();
|
|
17542
17909
|
if (content)
|
|
17543
17910
|
roots.push({
|
|
@@ -17604,10 +17971,6 @@ function scanRoot(root, diagnostics) {
|
|
|
17604
17971
|
}
|
|
17605
17972
|
return skills;
|
|
17606
17973
|
}
|
|
17607
|
-
function resolveBuiltinSkillDir() {
|
|
17608
|
-
const candidates = [join14(import.meta.dir, "library"), join14(import.meta.dir, "..", "..", "src", "agent-skills", "library")];
|
|
17609
|
-
return candidates.find((candidate) => existsSync9(candidate)) ?? candidates[0];
|
|
17610
|
-
}
|
|
17611
17974
|
function parseSkill(file, directory, directoryName, root, diagnostics) {
|
|
17612
17975
|
let raw;
|
|
17613
17976
|
try {
|
|
@@ -17812,11 +18175,10 @@ function compactText(value, max) {
|
|
|
17812
18175
|
function errorMessage(error) {
|
|
17813
18176
|
return error instanceof Error ? error.message : String(error);
|
|
17814
18177
|
}
|
|
17815
|
-
var
|
|
18178
|
+
var MAX_SKILL_BYTES, RECOMMENDED_SKILL_BYTES, MAX_RESOURCE_BYTES, MAX_RESOURCES = 256, NAME_PATTERN;
|
|
17816
18179
|
var init_registry2 = __esm(() => {
|
|
17817
18180
|
init_file_read();
|
|
17818
18181
|
init_paths2();
|
|
17819
|
-
BUILTIN_DIR = resolveBuiltinSkillDir();
|
|
17820
18182
|
MAX_SKILL_BYTES = 64 * 1024;
|
|
17821
18183
|
RECOMMENDED_SKILL_BYTES = 20 * 1024;
|
|
17822
18184
|
MAX_RESOURCE_BYTES = 128 * 1024;
|
|
@@ -17951,7 +18313,7 @@ var init_global_config = __esm(() => {
|
|
|
17951
18313
|
|
|
17952
18314
|
// src/agent-core/context-builder.ts
|
|
17953
18315
|
import { execFileSync } from "child_process";
|
|
17954
|
-
import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as
|
|
18316
|
+
import { existsSync as existsSync10, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
17955
18317
|
import { isAbsolute as isAbsolute6, join as join15, normalize as normalize2, relative as relative5 } from "path";
|
|
17956
18318
|
|
|
17957
18319
|
class ContextBuilderCache {
|
|
@@ -18205,7 +18567,7 @@ function workspaceFileFingerprint(workspace) {
|
|
|
18205
18567
|
}
|
|
18206
18568
|
function statFingerprint(path) {
|
|
18207
18569
|
try {
|
|
18208
|
-
const stat =
|
|
18570
|
+
const stat = statSync5(path);
|
|
18209
18571
|
return `${path}:${stat.mtimeMs}:${stat.size}:${stat.ino}`;
|
|
18210
18572
|
} catch {
|
|
18211
18573
|
return `${path}:missing`;
|
|
@@ -18219,7 +18581,7 @@ function validWorkspaceFiles(workspace, paths) {
|
|
|
18219
18581
|
continue;
|
|
18220
18582
|
const absolute = join15(workspace, rel);
|
|
18221
18583
|
try {
|
|
18222
|
-
if (
|
|
18584
|
+
if (statSync5(absolute).isFile())
|
|
18223
18585
|
files.push(rel);
|
|
18224
18586
|
} catch {}
|
|
18225
18587
|
}
|
|
@@ -22905,7 +23267,7 @@ var init_web = __esm(() => {
|
|
|
22905
23267
|
});
|
|
22906
23268
|
|
|
22907
23269
|
// src/agent-tools/media/image-view.ts
|
|
22908
|
-
import { statSync as
|
|
23270
|
+
import { statSync as statSync6 } from "fs";
|
|
22909
23271
|
import { basename as basename3, relative as relative6 } from "path";
|
|
22910
23272
|
function detectImage(data) {
|
|
22911
23273
|
if (data.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
@@ -22954,7 +23316,7 @@ var init_image_view = __esm(() => {
|
|
|
22954
23316
|
assertObject(args, "args");
|
|
22955
23317
|
const requested = asString(args.path, "path");
|
|
22956
23318
|
const path = safeExistingWorkspacePath(context.workspace, requested, "read");
|
|
22957
|
-
const stat =
|
|
23319
|
+
const stat = statSync6(path);
|
|
22958
23320
|
if (!stat.isFile())
|
|
22959
23321
|
throw new Error(`not a file: ${requested}`);
|
|
22960
23322
|
if (stat.size > MAX_IMAGE_BYTES)
|
|
@@ -34806,6 +35168,7 @@ class AgentRuntime {
|
|
|
34806
35168
|
for (const controller of this.subagentControllers.values())
|
|
34807
35169
|
controller.abort("runtime shutdown");
|
|
34808
35170
|
const drain = Promise.allSettled([...[...this.actors.values()].map((actor) => actor.idle()), this.workspaceBindingGate.idle(), this.toolExecutionGate.idle(), this.subagentGate.idle(), this.subagentWorkspaceMutationGate.idle(), ...this.recoveryPromise ? [this.recoveryPromise] : []]);
|
|
35171
|
+
const drainGracePeriod = shutdownGracePeriod(options.gracePeriodMs);
|
|
34809
35172
|
this.shutdownFinalizationPromise = (async () => {
|
|
34810
35173
|
const failures = [];
|
|
34811
35174
|
const attempt = async (label, operation) => {
|
|
@@ -34818,7 +35181,11 @@ class AgentRuntime {
|
|
|
34818
35181
|
}
|
|
34819
35182
|
};
|
|
34820
35183
|
try {
|
|
34821
|
-
await drain;
|
|
35184
|
+
const drained = await waitForShutdownFinalization(drain, drainGracePeriod);
|
|
35185
|
+
if (!drained) {
|
|
35186
|
+
for (const lease of this.activeToolLeases)
|
|
35187
|
+
lease.revoke("runtime shutdown grace period expired");
|
|
35188
|
+
}
|
|
34822
35189
|
this.activeToolControllers.clear();
|
|
34823
35190
|
this.activeToolLeases.clear();
|
|
34824
35191
|
this.subagentControllers.clear();
|
|
@@ -34995,6 +35362,10 @@ class AgentRuntime {
|
|
|
34995
35362
|
await this.recoveryPromise;
|
|
34996
35363
|
} catch (error) {
|
|
34997
35364
|
this.stopRuntimeLease();
|
|
35365
|
+
if (this.shuttingDown)
|
|
35366
|
+
throw new Error("Farai runtime is shutting down", {
|
|
35367
|
+
cause: error
|
|
35368
|
+
});
|
|
34998
35369
|
throw error;
|
|
34999
35370
|
} finally {
|
|
35000
35371
|
this.recoveryPromise = undefined;
|
|
@@ -39335,6 +39706,7 @@ function parseContentManifest(value, baseUrl) {
|
|
|
39335
39706
|
throw new Error("invalid content version");
|
|
39336
39707
|
if (typeof record3.generatedAt !== "string" || !Number.isFinite(Date.parse(record3.generatedAt)))
|
|
39337
39708
|
throw new Error("invalid content generatedAt");
|
|
39709
|
+
const sourceCommit = optionalSourceCommit(record3.sourceCommit);
|
|
39338
39710
|
const minFaraiVersion = optionalString2(record3.minFaraiVersion, 128, "minFaraiVersion");
|
|
39339
39711
|
if (minFaraiVersion && !isSemver(minFaraiVersion))
|
|
39340
39712
|
throw new Error("invalid content minFaraiVersion");
|
|
@@ -39345,6 +39717,9 @@ function parseContentManifest(value, baseUrl) {
|
|
|
39345
39717
|
schemaVersion: 1,
|
|
39346
39718
|
contentVersion: record3.contentVersion,
|
|
39347
39719
|
generatedAt: new Date(record3.generatedAt).toISOString(),
|
|
39720
|
+
...sourceCommit ? {
|
|
39721
|
+
sourceCommit
|
|
39722
|
+
} : {},
|
|
39348
39723
|
...minFaraiVersion ? {
|
|
39349
39724
|
minFaraiVersion
|
|
39350
39725
|
} : {},
|
|
@@ -39359,6 +39734,13 @@ function parseContentManifest(value, baseUrl) {
|
|
|
39359
39734
|
} : {}
|
|
39360
39735
|
};
|
|
39361
39736
|
}
|
|
39737
|
+
function optionalSourceCommit(value) {
|
|
39738
|
+
if (value === undefined)
|
|
39739
|
+
return;
|
|
39740
|
+
if (typeof value !== "string" || !SOURCE_COMMIT_PATTERN2.test(value))
|
|
39741
|
+
throw new Error("invalid content sourceCommit");
|
|
39742
|
+
return value;
|
|
39743
|
+
}
|
|
39362
39744
|
function artifact(value, baseUrl, label) {
|
|
39363
39745
|
if (value === undefined)
|
|
39364
39746
|
return;
|
|
@@ -39395,13 +39777,14 @@ function optionalString2(value, maxLength, label) {
|
|
|
39395
39777
|
throw new Error(`invalid content ${label}`);
|
|
39396
39778
|
return value.trim();
|
|
39397
39779
|
}
|
|
39398
|
-
var CONTENT_MANIFEST_SCHEMA_VERSION = 1, CONTENT_MANIFEST_MAX_BYTES, DEFAULT_CONTENT_MANIFEST_URL = "https://github.com/pajarori/farai-data/releases/latest/
|
|
39780
|
+
var CONTENT_MANIFEST_SCHEMA_VERSION = 1, CONTENT_MANIFEST_MAX_BYTES, DEFAULT_CONTENT_MANIFEST_URL = "https://github.com/pajarori/farai-data/releases/download/latest/manifest.json", CONTENT_VERSION_PATTERN, SHA256_PATTERN, SOURCE_COMMIT_PATTERN2;
|
|
39399
39781
|
var init_manifest = __esm(() => {
|
|
39400
39782
|
init_http_response();
|
|
39401
39783
|
init_file_read();
|
|
39402
39784
|
CONTENT_MANIFEST_MAX_BYTES = 256 * 1024;
|
|
39403
39785
|
CONTENT_VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
39404
39786
|
SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
39787
|
+
SOURCE_COMMIT_PATTERN2 = /^[a-f0-9]{40}$/;
|
|
39405
39788
|
});
|
|
39406
39789
|
|
|
39407
39790
|
// src/agent-content/updater.ts
|
|
@@ -39417,7 +39800,7 @@ __export(exports_updater, {
|
|
|
39417
39800
|
CONTENT_MANIFEST_CACHE_TTL_MS: () => CONTENT_MANIFEST_CACHE_TTL_MS
|
|
39418
39801
|
});
|
|
39419
39802
|
import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
|
|
39420
|
-
import { closeSync as closeSync4, existsSync as existsSync18, lstatSync as lstatSync4, mkdirSync as mkdirSync7, openSync as openSync4, readSync as readSync2, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync3, statSync as
|
|
39803
|
+
import { closeSync as closeSync4, existsSync as existsSync18, lstatSync as lstatSync4, mkdirSync as mkdirSync7, openSync as openSync4, readSync as readSync2, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync3, statSync as statSync7, unlinkSync as unlinkSync6, writeSync } from "fs";
|
|
39421
39804
|
import { dirname as dirname9, join as join23 } from "path";
|
|
39422
39805
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
39423
39806
|
async function checkContentUpdate(options = {}) {
|
|
@@ -39592,6 +39975,9 @@ async function applyContentUpdate(manifest, manifestUrl, options = {}) {
|
|
|
39592
39975
|
generatedAt: manifest.generatedAt,
|
|
39593
39976
|
activatedAt: new Date().toISOString(),
|
|
39594
39977
|
manifestUrl,
|
|
39978
|
+
...manifest.sourceCommit ? {
|
|
39979
|
+
sourceCommit: manifest.sourceCommit
|
|
39980
|
+
} : {},
|
|
39595
39981
|
...previous && previous.version !== manifest.contentVersion ? {
|
|
39596
39982
|
previousVersion: previous.version
|
|
39597
39983
|
} : {},
|
|
@@ -39604,6 +39990,9 @@ async function applyContentUpdate(manifest, manifestUrl, options = {}) {
|
|
|
39604
39990
|
pruneVersions(pointer);
|
|
39605
39991
|
return {
|
|
39606
39992
|
version: pointer.version,
|
|
39993
|
+
...pointer.sourceCommit ? {
|
|
39994
|
+
sourceCommit: pointer.sourceCommit
|
|
39995
|
+
} : {},
|
|
39607
39996
|
...pointer.previousVersion ? {
|
|
39608
39997
|
previousVersion: pointer.previousVersion
|
|
39609
39998
|
} : {},
|
|
@@ -39637,6 +40026,9 @@ function rollbackContentUpdate() {
|
|
|
39637
40026
|
generatedAt: manifest.generatedAt,
|
|
39638
40027
|
activatedAt: new Date().toISOString(),
|
|
39639
40028
|
manifestUrl: active.manifestUrl,
|
|
40029
|
+
...manifest.sourceCommit ? {
|
|
40030
|
+
sourceCommit: manifest.sourceCommit
|
|
40031
|
+
} : {},
|
|
39640
40032
|
previousVersion: active.version,
|
|
39641
40033
|
knowledge: Boolean(manifest.knowledge && existsSync18(join23(previousPath, "knowledge.db"))),
|
|
39642
40034
|
skills: Boolean(manifest.skills && existsSync18(join23(previousPath, "skills")))
|
|
@@ -39645,6 +40037,9 @@ function rollbackContentUpdate() {
|
|
|
39645
40037
|
`, 384);
|
|
39646
40038
|
return {
|
|
39647
40039
|
version: next.version,
|
|
40040
|
+
...next.sourceCommit ? {
|
|
40041
|
+
sourceCommit: next.sourceCommit
|
|
40042
|
+
} : {},
|
|
39648
40043
|
previousVersion: next.previousVersion,
|
|
39649
40044
|
knowledge: next.knowledge,
|
|
39650
40045
|
skills: next.skills,
|
|
@@ -39696,6 +40091,10 @@ function contentUpdateDisabled(configured) {
|
|
|
39696
40091
|
function isNewerManifest(manifest, active) {
|
|
39697
40092
|
if (!active)
|
|
39698
40093
|
return true;
|
|
40094
|
+
if (manifest.sourceCommit && active.sourceCommit)
|
|
40095
|
+
return manifest.sourceCommit !== active.sourceCommit;
|
|
40096
|
+
if (manifest.sourceCommit && !active.sourceCommit)
|
|
40097
|
+
return true;
|
|
39699
40098
|
if (manifest.contentVersion === active.version)
|
|
39700
40099
|
return false;
|
|
39701
40100
|
const generated = Date.parse(manifest.generatedAt);
|
|
@@ -39771,7 +40170,7 @@ function acquireLock() {
|
|
|
39771
40170
|
}
|
|
39772
40171
|
function staleLock(path) {
|
|
39773
40172
|
try {
|
|
39774
|
-
const stats =
|
|
40173
|
+
const stats = statSync7(path);
|
|
39775
40174
|
const parsed = JSON.parse(readBoundedFileTextSyncNoFollow(path, 4 * 1024, "content update lock"));
|
|
39776
40175
|
if (typeof parsed.pid === "number" && parsed.pid > 0) {
|
|
39777
40176
|
try {
|
|
@@ -67344,16 +67743,18 @@ async function runContentUpdateCommand(parsed, workspace) {
|
|
|
67344
67743
|
const status3 = contentStatus();
|
|
67345
67744
|
if (!status3.active) {
|
|
67346
67745
|
const knowledge = legacyKnowledgeDbPath();
|
|
67347
|
-
console.log("content:
|
|
67746
|
+
console.log("content: not installed");
|
|
67348
67747
|
console.log("active release: none");
|
|
67349
67748
|
console.log(`knowledge: ${existsSync26(knowledge) ? knowledge : "not installed"}`);
|
|
67350
|
-
console.log("skills:
|
|
67749
|
+
console.log("skills: not installed");
|
|
67351
67750
|
return 0;
|
|
67352
67751
|
}
|
|
67353
67752
|
console.log(`content: ${status3.active.version}`);
|
|
67753
|
+
if (status3.active.sourceCommit)
|
|
67754
|
+
console.log(`source commit: ${status3.active.sourceCommit}`);
|
|
67354
67755
|
console.log(`activated: ${status3.active.activatedAt}`);
|
|
67355
67756
|
console.log(`knowledge: ${status3.knowledgePath ?? "local fallback"}`);
|
|
67356
|
-
console.log(`skills: ${status3.skillsPath ?? "
|
|
67757
|
+
console.log(`skills: ${status3.skillsPath ?? "not installed"}`);
|
|
67357
67758
|
console.log(`available versions: ${status3.versions.join(", ") || "none"}`);
|
|
67358
67759
|
return 0;
|
|
67359
67760
|
}
|
|
@@ -67381,6 +67782,8 @@ async function runContentUpdateCommand(parsed, workspace) {
|
|
|
67381
67782
|
function printContentUpdateStatus(status2) {
|
|
67382
67783
|
if (status2.state === "update_available") {
|
|
67383
67784
|
console.log(`content update available: ${status2.active?.version ?? "none"} -> ${status2.manifest?.contentVersion}`);
|
|
67785
|
+
if (status2.manifest?.sourceCommit)
|
|
67786
|
+
console.log(`source commit: ${status2.manifest.sourceCommit}`);
|
|
67384
67787
|
return 0;
|
|
67385
67788
|
}
|
|
67386
67789
|
if (status2.state === "up_to_date") {
|
|
@@ -68145,7 +68548,7 @@ __export(exports_csi_suite, {
|
|
|
68145
68548
|
loadCsiCampaignConfig: () => loadCsiCampaignConfig,
|
|
68146
68549
|
generateCsiBenchmarkSuite: () => generateCsiBenchmarkSuite
|
|
68147
68550
|
});
|
|
68148
|
-
import { existsSync as existsSync27, readdirSync as readdirSync13, statSync as
|
|
68551
|
+
import { existsSync as existsSync27, readdirSync as readdirSync13, statSync as statSync8 } from "fs";
|
|
68149
68552
|
import { dirname as dirname12, isAbsolute as isAbsolute9, join as join35, relative as relative11, resolve as resolve11 } from "path";
|
|
68150
68553
|
async function loadCsiCampaignConfig(path) {
|
|
68151
68554
|
return normalizeCsiCampaignConfig(JSON.parse(await readBoundedFileText(path, CSI_CAMPAIGN_MAX_BYTES, "csi campaign config")));
|
|
@@ -68171,7 +68574,7 @@ async function generateCsiBenchmarkSuite(configInput, materialRoot) {
|
|
|
68171
68574
|
if (config.isolation.backend === "host" && material.requiresTarget)
|
|
68172
68575
|
throw new Error(`host csi challenge requires a live target and cannot run in host smoke mode: ${challenge.id}`);
|
|
68173
68576
|
const promptPath = protectedPath(root, material.promptFile, `${challenge.id}.promptFile`);
|
|
68174
|
-
if (!existsSync27(promptPath) || !
|
|
68577
|
+
if (!existsSync27(promptPath) || !statSync8(promptPath).isFile())
|
|
68175
68578
|
throw new Error(`missing prompt file for csi challenge: ${challenge.id}`);
|
|
68176
68579
|
const prompt = readBoundedFileTextSync(promptPath, CSI_PROMPT_MAX_BYTES, `csi prompt ${challenge.id}`).trim();
|
|
68177
68580
|
if (!prompt)
|
|
@@ -68180,7 +68583,7 @@ async function generateCsiBenchmarkSuite(configInput, materialRoot) {
|
|
|
68180
68583
|
const source = protectedPath(root, file.source, `${challenge.id}.files[${index}].source`);
|
|
68181
68584
|
if (!existsSync27(source))
|
|
68182
68585
|
throw new Error(`missing input for csi challenge ${challenge.id}: ${file.source}`);
|
|
68183
|
-
if (
|
|
68586
|
+
if (statSync8(source).isDirectory() && !listFiles(source).length)
|
|
68184
68587
|
throw new Error(`empty input directory for csi challenge ${challenge.id}: ${file.source}`);
|
|
68185
68588
|
const digest2 = hashPath(source);
|
|
68186
68589
|
if (file.sha256 && file.sha256.toLowerCase() !== digest2)
|
|
@@ -68200,14 +68603,14 @@ async function generateCsiBenchmarkSuite(configInput, materialRoot) {
|
|
|
68200
68603
|
throw new Error(`missing required protected files for csi challenge ${challenge.id}: ${missing.join(", ")}`);
|
|
68201
68604
|
}
|
|
68202
68605
|
const executable = protectedPath(root, material.oracle.executable, `${challenge.id}.oracle.executable`);
|
|
68203
|
-
if (!existsSync27(executable) || !
|
|
68606
|
+
if (!existsSync27(executable) || !statSync8(executable).isFile())
|
|
68204
68607
|
throw new Error(`missing oracle executable for csi challenge: ${challenge.id}`);
|
|
68205
|
-
if ((
|
|
68608
|
+
if ((statSync8(executable).mode & 73) === 0)
|
|
68206
68609
|
throw new Error(`oracle executable is not executable for csi challenge: ${challenge.id}`);
|
|
68207
68610
|
const antiCheatExecutable = material.antiCheat ? protectedPath(root, material.antiCheat.executable, `${challenge.id}.antiCheat.executable`) : undefined;
|
|
68208
|
-
if (antiCheatExecutable && (!existsSync27(antiCheatExecutable) || !
|
|
68611
|
+
if (antiCheatExecutable && (!existsSync27(antiCheatExecutable) || !statSync8(antiCheatExecutable).isFile()))
|
|
68209
68612
|
throw new Error(`missing anti-cheat executable for csi challenge: ${challenge.id}`);
|
|
68210
|
-
if (antiCheatExecutable && (
|
|
68613
|
+
if (antiCheatExecutable && (statSync8(antiCheatExecutable).mode & 73) === 0)
|
|
68211
68614
|
throw new Error(`anti-cheat executable is not executable for csi challenge: ${challenge.id}`);
|
|
68212
68615
|
if (config.isolation.backend === "docker" && !material.target)
|
|
68213
68616
|
throw new Error(`docker csi challenge requires a pinned target image: ${challenge.id}`);
|
|
@@ -68416,7 +68819,7 @@ function protectedPath(root, path, name) {
|
|
|
68416
68819
|
return resolved;
|
|
68417
68820
|
}
|
|
68418
68821
|
function listFiles(rootPath) {
|
|
68419
|
-
if (!
|
|
68822
|
+
if (!statSync8(rootPath).isDirectory())
|
|
68420
68823
|
return [rootPath];
|
|
68421
68824
|
return readdirSync13(rootPath).flatMap((name) => listFiles(join35(rootPath, name)));
|
|
68422
68825
|
}
|
|
@@ -68539,6 +68942,36 @@ class BenchmarkDockerLifecycle {
|
|
|
68539
68942
|
this.runner = runner;
|
|
68540
68943
|
}
|
|
68541
68944
|
async start() {
|
|
68945
|
+
if (this.startPromise)
|
|
68946
|
+
return this.startPromise;
|
|
68947
|
+
const waitedForStop = Boolean(this.stopPromise);
|
|
68948
|
+
if (this.stopPromise)
|
|
68949
|
+
await this.stopPromise;
|
|
68950
|
+
if (!waitedForStop && this.stateValue?.started && !this.stateValue.cleaned && this.planValue) {
|
|
68951
|
+
return {
|
|
68952
|
+
backend: new KaliContainerBackend({
|
|
68953
|
+
workspace: this.workspace,
|
|
68954
|
+
image: DEFAULT_KALI_IMAGE,
|
|
68955
|
+
containerName: this.planValue.names.agent,
|
|
68956
|
+
processRunner: (command, args2) => this.runner(command, args2)
|
|
68957
|
+
}),
|
|
68958
|
+
state: this.stateValue,
|
|
68959
|
+
plan: this.planValue
|
|
68960
|
+
};
|
|
68961
|
+
}
|
|
68962
|
+
if (this.stateValue && !this.stateValue.cleaned) {
|
|
68963
|
+
const previous = await this.stopUnlocked();
|
|
68964
|
+
if (previous && !previous.cleaned)
|
|
68965
|
+
throw new Error("previous benchmark Docker resources could not be cleaned up");
|
|
68966
|
+
}
|
|
68967
|
+
this.startPromise = this.startUnlocked();
|
|
68968
|
+
try {
|
|
68969
|
+
return await this.startPromise;
|
|
68970
|
+
} finally {
|
|
68971
|
+
this.startPromise = undefined;
|
|
68972
|
+
}
|
|
68973
|
+
}
|
|
68974
|
+
async startUnlocked() {
|
|
68542
68975
|
const processRunner = (command, args2) => this.runner(command, args2);
|
|
68543
68976
|
const image = await new KaliContainerBackend({
|
|
68544
68977
|
workspace: this.workspace,
|
|
@@ -68546,14 +68979,17 @@ class BenchmarkDockerLifecycle {
|
|
|
68546
68979
|
processRunner
|
|
68547
68980
|
}).resolveImage();
|
|
68548
68981
|
if (!image.exists)
|
|
68549
|
-
throw new Error(`benchmark agent image is missing: ${DEFAULT_KALI_IMAGE}`);
|
|
68982
|
+
throw new Error(image.error ?? `benchmark agent image is missing: ${DEFAULT_KALI_IMAGE}`);
|
|
68983
|
+
if (image.error)
|
|
68984
|
+
throw new Error(image.error);
|
|
68550
68985
|
const agentImageId = image.id?.trim() ?? "";
|
|
68551
68986
|
if (!/^sha256:[a-f0-9]{64}$/i.test(agentImageId))
|
|
68552
68987
|
throw new Error(`docker returned an unpinned agent image id: ${agentImageId || "empty"}`);
|
|
68553
68988
|
const agentImageContract = image.contract?.trim() ?? "";
|
|
68554
68989
|
if (agentImageContract !== KALI_IMAGE_CONTRACT)
|
|
68555
68990
|
throw new Error(`benchmark agent image does not satisfy the current capability contract: ${agentImageContract || "missing"}`);
|
|
68556
|
-
const
|
|
68991
|
+
const targetImage = await resolveTargetImage(this.manifest, this.runner);
|
|
68992
|
+
const plan = buildBenchmarkDockerPlan(this.manifest, this.workspace, this.runId, agentImageId, targetImage);
|
|
68557
68993
|
this.planValue = plan;
|
|
68558
68994
|
const state = {
|
|
68559
68995
|
network: plan.names.network,
|
|
@@ -68561,6 +68997,7 @@ class BenchmarkDockerLifecycle {
|
|
|
68561
68997
|
agentContainer: plan.names.agent,
|
|
68562
68998
|
agentImageId,
|
|
68563
68999
|
agentImageContract,
|
|
69000
|
+
targetImage,
|
|
68564
69001
|
started: false,
|
|
68565
69002
|
antiCheatApplied: false,
|
|
68566
69003
|
cleaned: false,
|
|
@@ -68592,27 +69029,49 @@ class BenchmarkDockerLifecycle {
|
|
|
68592
69029
|
};
|
|
68593
69030
|
} catch (error) {
|
|
68594
69031
|
state.errors.push(error instanceof Error ? error.message : String(error));
|
|
68595
|
-
await this.
|
|
69032
|
+
await this.stopUnlocked();
|
|
68596
69033
|
throw error;
|
|
68597
69034
|
}
|
|
68598
69035
|
}
|
|
68599
69036
|
async stop() {
|
|
69037
|
+
if (this.startPromise)
|
|
69038
|
+
await this.startPromise.catch(() => {
|
|
69039
|
+
return;
|
|
69040
|
+
});
|
|
69041
|
+
if (this.stopPromise)
|
|
69042
|
+
return this.stopPromise;
|
|
69043
|
+
this.stopPromise = this.stopUnlocked();
|
|
69044
|
+
try {
|
|
69045
|
+
return await this.stopPromise;
|
|
69046
|
+
} finally {
|
|
69047
|
+
this.stopPromise = undefined;
|
|
69048
|
+
}
|
|
69049
|
+
}
|
|
69050
|
+
async stopUnlocked() {
|
|
68600
69051
|
const state = this.stateValue;
|
|
68601
69052
|
const plan = this.planValue;
|
|
68602
69053
|
if (!state || !plan || state.cleaned)
|
|
68603
69054
|
return state;
|
|
68604
|
-
const agentState = await this.inspectState(plan.names.agent);
|
|
68605
|
-
const targetState = await this.inspectState(plan.names.target);
|
|
69055
|
+
const [agentState, targetState] = await Promise.all([this.inspectState(plan.names.agent), this.inspectState(plan.names.target)]);
|
|
68606
69056
|
if (agentState)
|
|
68607
69057
|
state.agentState = agentState;
|
|
68608
69058
|
if (targetState)
|
|
68609
69059
|
state.targetState = targetState;
|
|
69060
|
+
let cleaned = true;
|
|
68610
69061
|
for (const args2 of plan.cleanup) {
|
|
68611
|
-
|
|
68612
|
-
|
|
68613
|
-
|
|
69062
|
+
try {
|
|
69063
|
+
const result = await this.runner("docker", args2);
|
|
69064
|
+
if (result.exitCode !== 0 && !resourceDoesNotExist(result)) {
|
|
69065
|
+
cleaned = false;
|
|
69066
|
+
if (result.stderr.trim())
|
|
69067
|
+
state.errors.push(result.stderr.trim().slice(0, 500));
|
|
69068
|
+
}
|
|
69069
|
+
} catch (error) {
|
|
69070
|
+
cleaned = false;
|
|
69071
|
+
state.errors.push(error instanceof Error ? error.message.slice(0, 500) : String(error).slice(0, 500));
|
|
69072
|
+
}
|
|
68614
69073
|
}
|
|
68615
|
-
state.cleaned =
|
|
69074
|
+
state.cleaned = cleaned;
|
|
68616
69075
|
return state;
|
|
68617
69076
|
}
|
|
68618
69077
|
async requiredDocker(args2, operation) {
|
|
@@ -68621,10 +69080,10 @@ class BenchmarkDockerLifecycle {
|
|
|
68621
69080
|
throw new Error(result.stderr || `failed to ${operation}`);
|
|
68622
69081
|
}
|
|
68623
69082
|
async inspectState(name) {
|
|
68624
|
-
const result = await this.runner("docker", ["inspect", "--format", "{{json .State}}", name]);
|
|
68625
|
-
if (result.exitCode !== 0)
|
|
68626
|
-
return;
|
|
68627
69083
|
try {
|
|
69084
|
+
const result = await this.runner("docker", ["inspect", "--format", "{{json .State}}", name]);
|
|
69085
|
+
if (result.exitCode !== 0)
|
|
69086
|
+
return;
|
|
68628
69087
|
const state = JSON.parse(result.stdout);
|
|
68629
69088
|
if (typeof state.Running !== "boolean" || typeof state.ExitCode !== "number")
|
|
68630
69089
|
return;
|
|
@@ -68637,7 +69096,7 @@ class BenchmarkDockerLifecycle {
|
|
|
68637
69096
|
}
|
|
68638
69097
|
}
|
|
68639
69098
|
}
|
|
68640
|
-
function buildBenchmarkDockerPlan(manifest, workspace, runId, agentImageId) {
|
|
69099
|
+
function buildBenchmarkDockerPlan(manifest, workspace, runId, agentImageId, targetImageOverride) {
|
|
68641
69100
|
if (manifest.isolation.backend !== "docker")
|
|
68642
69101
|
throw new Error("benchmark docker plan requires isolation.backend=docker");
|
|
68643
69102
|
if (manifest.isolation.network !== "target_only" || manifest.isolation.internet !== "disabled") {
|
|
@@ -68664,12 +69123,13 @@ function buildBenchmarkDockerPlan(manifest, workspace, runId, agentImageId) {
|
|
|
68664
69123
|
throw new Error("docker benchmark diskMb is not enforceable for a bind-mounted scratch workspace");
|
|
68665
69124
|
const common = ["--security-opt", "no-new-privileges:true", ...resources?.pids ? ["--pids-limit", String(resources.pids)] : []];
|
|
68666
69125
|
const resourceArgs = [...resources?.cpus ? ["--cpus", String(resources.cpus)] : [], ...resources?.memoryMb ? ["--memory", `${resources.memoryMb}m`] : []];
|
|
68667
|
-
const targetImage = pinnedImage(manifest.challenge.targetImage, manifest.challenge.targetImageDigest);
|
|
69126
|
+
const targetImage = targetImageOverride ?? pinnedImage(manifest.challenge.targetImage, manifest.challenge.targetImageDigest);
|
|
68668
69127
|
const targetStart = ["run", "-d", "--name", names.target, "--network", names.network, "--network-alias", "target", "--cap-drop", "ALL", "--cap-add", "NET_BIND_SERVICE", ...common, ...resourceArgs, targetImage, ...manifest.challenge.targetCommand ?? []];
|
|
68669
69128
|
const resolvedWorkspace = resolve12(workspace);
|
|
68670
69129
|
const agentStart = ["run", "-d", "--name", names.agent, "--network", names.network, "--workdir", "/workspace", "--volume", `${resolvedWorkspace}:/workspace:rw`, "--volume", "/workspace/.farai", "--read-only", "--tmpfs", "/tmp:rw,nosuid,nodev,size=512m", "--tmpfs", "/root:rw,nosuid,nodev,size=256m", "--tmpfs", "/run:rw,nosuid,nodev,size=64m", "--cap-drop", "ALL", "--cap-add", "NET_ADMIN", "--cap-add", "NET_RAW", ...common, ...resourceArgs, agentImageId, "sleep", "infinity"];
|
|
68671
69130
|
return {
|
|
68672
69131
|
names,
|
|
69132
|
+
targetImage,
|
|
68673
69133
|
networkCreate: ["network", "create", "--internal", "--label", "org.farai.benchmark=true", names.network],
|
|
68674
69134
|
targetStart,
|
|
68675
69135
|
agentStart,
|
|
@@ -68686,6 +69146,51 @@ function buildBenchmarkDockerPlan(manifest, workspace, runId, agentImageId) {
|
|
|
68686
69146
|
cleanup: [["rm", "-f", "-v", names.agent], ["rm", "-f", "-v", names.target], ["network", "rm", names.network]]
|
|
68687
69147
|
};
|
|
68688
69148
|
}
|
|
69149
|
+
async function resolveTargetImage(manifest, runner) {
|
|
69150
|
+
if (!manifest.challenge.targetImage || !manifest.challenge.targetImageDigest)
|
|
69151
|
+
throw new Error("docker benchmark requires a pinned target image");
|
|
69152
|
+
const pinned = pinnedImage(manifest.challenge.targetImage, manifest.challenge.targetImageDigest);
|
|
69153
|
+
const exact = await runner("docker", ["image", "inspect", pinned]);
|
|
69154
|
+
if (exact.exitCode === 0)
|
|
69155
|
+
return imageIdFromInspect(exact.stdout) ?? pinned;
|
|
69156
|
+
const base = manifest.challenge.targetImage.split("@")[0];
|
|
69157
|
+
const tagged = await runner("docker", ["image", "inspect", base]);
|
|
69158
|
+
if (tagged.exitCode === 0) {
|
|
69159
|
+
const imageId = imageIdForDigest(tagged.stdout, pinned);
|
|
69160
|
+
if (imageId)
|
|
69161
|
+
return imageId;
|
|
69162
|
+
}
|
|
69163
|
+
const details = [exact.stderr, tagged.stderr].map((value) => value.trim()).find(Boolean);
|
|
69164
|
+
throw new Error(`benchmark target image is unavailable locally: ${pinned}; build or load the pinned target image before running the benchmark${details ? ` (${details.slice(0, 300)})` : ""}`);
|
|
69165
|
+
}
|
|
69166
|
+
function imageIdForDigest(raw, pinned) {
|
|
69167
|
+
try {
|
|
69168
|
+
const images = JSON.parse(raw);
|
|
69169
|
+
const image = images[0];
|
|
69170
|
+
if (!image || !Array.isArray(image.RepoDigests))
|
|
69171
|
+
return;
|
|
69172
|
+
const [repository, digest2] = pinned.split("@");
|
|
69173
|
+
const repositoryWithoutTag = repository?.replace(/:[^/:]+$/, "");
|
|
69174
|
+
const matches2 = image.RepoDigests.some((value) => typeof value === "string" && (value.toLowerCase() === pinned.toLowerCase() || repositoryWithoutTag && digest2 && value.toLowerCase() === `${repositoryWithoutTag}@${digest2}`.toLowerCase()));
|
|
69175
|
+
if (!matches2 || typeof image.Id !== "string" || !/^sha256:[a-f0-9]{64}$/i.test(image.Id))
|
|
69176
|
+
return;
|
|
69177
|
+
return image.Id;
|
|
69178
|
+
} catch {
|
|
69179
|
+
return;
|
|
69180
|
+
}
|
|
69181
|
+
}
|
|
69182
|
+
function imageIdFromInspect(raw) {
|
|
69183
|
+
try {
|
|
69184
|
+
const id2 = JSON.parse(raw)[0]?.Id;
|
|
69185
|
+
return typeof id2 === "string" && /^sha256:[a-f0-9]{64}$/i.test(id2) ? id2 : undefined;
|
|
69186
|
+
} catch {
|
|
69187
|
+
return;
|
|
69188
|
+
}
|
|
69189
|
+
}
|
|
69190
|
+
function resourceDoesNotExist(result) {
|
|
69191
|
+
return /no such (container|network|object)|(?:container|network)\s+[^\n]*\bnot found\b/i.test(`${result.stdout}
|
|
69192
|
+
${result.stderr}`);
|
|
69193
|
+
}
|
|
68689
69194
|
function pinnedImage(image, digest2) {
|
|
68690
69195
|
const normalizedDigest = digest2.startsWith("sha256:") ? digest2 : `sha256:${digest2}`;
|
|
68691
69196
|
if (!/^sha256:[a-f0-9]{64}$/i.test(normalizedDigest))
|
|
@@ -70377,12 +70882,15 @@ async function setup(args2) {
|
|
|
70377
70882
|
console.log("[*] skipping Docker image build");
|
|
70378
70883
|
}
|
|
70379
70884
|
if (!parsed.skipKnowledge) {
|
|
70380
|
-
|
|
70381
|
-
|
|
70382
|
-
|
|
70383
|
-
|
|
70384
|
-
|
|
70385
|
-
|
|
70885
|
+
const contentInstalled = await syncContentForSetup(process.cwd());
|
|
70886
|
+
if (!contentInstalled) {
|
|
70887
|
+
console.log("[*] building Farai knowledge base");
|
|
70888
|
+
const code = await (await Promise.resolve().then(() => (init_command(), exports_command))).runKbCommand(["build", "all"]);
|
|
70889
|
+
if (code !== 0) {
|
|
70890
|
+
process.exitCode = code;
|
|
70891
|
+
console.error("[!] knowledge base build failed");
|
|
70892
|
+
return;
|
|
70893
|
+
}
|
|
70386
70894
|
}
|
|
70387
70895
|
} else {
|
|
70388
70896
|
console.log("[*] skipping knowledge base build");
|
|
@@ -70390,6 +70898,30 @@ async function setup(args2) {
|
|
|
70390
70898
|
console.log("[+] setup complete");
|
|
70391
70899
|
console.log("[+] run `farai doctor` to verify the environment");
|
|
70392
70900
|
}
|
|
70901
|
+
async function syncContentForSetup(workspace) {
|
|
70902
|
+
const {
|
|
70903
|
+
applyContentUpdate: applyContentUpdate2,
|
|
70904
|
+
checkContentUpdate: checkContentUpdate2,
|
|
70905
|
+
contentStatus: contentStatus2
|
|
70906
|
+
} = await Promise.resolve().then(() => (init_updater(), exports_updater));
|
|
70907
|
+
try {
|
|
70908
|
+
const status2 = await checkContentUpdate2({
|
|
70909
|
+
workspace,
|
|
70910
|
+
force: true
|
|
70911
|
+
});
|
|
70912
|
+
if (status2.state === "update_available" && status2.manifest) {
|
|
70913
|
+
console.log(`[*] syncing Farai content ${status2.manifest.contentVersion}`);
|
|
70914
|
+
const applied = await applyContentUpdate2(status2.manifest, status2.manifestUrl);
|
|
70915
|
+
const parts = [applied.knowledge ? "knowledge" : undefined, applied.skills ? "skills" : undefined].filter(Boolean).join(" + ");
|
|
70916
|
+
console.log(`[+] content: ${applied.version}${parts ? ` (${parts})` : ""}`);
|
|
70917
|
+
} else if (status2.state === "error") {
|
|
70918
|
+
console.error(`[!] content sync unavailable: ${status2.error ?? "unknown error"}`);
|
|
70919
|
+
}
|
|
70920
|
+
} catch (error) {
|
|
70921
|
+
console.error(`[!] content sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
70922
|
+
}
|
|
70923
|
+
return Boolean(contentStatus2().active?.knowledge);
|
|
70924
|
+
}
|
|
70393
70925
|
async function models(args2 = []) {
|
|
70394
70926
|
const parsed = parseModelArguments(args2);
|
|
70395
70927
|
ensureDefaultUserConfig();
|
|
@@ -70727,5 +71259,5 @@ Examples:
|
|
|
70727
71259
|
`);
|
|
70728
71260
|
}
|
|
70729
71261
|
|
|
70730
|
-
//# debugId=
|
|
71262
|
+
//# debugId=ABD822216062EDDD64756E2164756E21
|
|
70731
71263
|
//# sourceMappingURL=index.js.map
|